aravind changes
This commit is contained in:
@@ -129,6 +129,125 @@ these would leave the shim with methods that 404. See §11 (D6).
|
||||
|
||||
---
|
||||
|
||||
## 2A. Owliver suggestions
|
||||
|
||||
`GET /api/v1/owliver/suggestions?page={surface}&query={typed}`
|
||||
|
||||
The one endpoint here that serves no resource. It answers "what could I usefully
|
||||
ask on this page?" for the Owliver panel, which calls it while the user types —
|
||||
so it reads no table, opens no transaction, calls no model, and its whole answer
|
||||
is computed from a static catalogue in `internal/owliver`.
|
||||
|
||||
It is not on the public allowlist. Which readings exist depends on the caller's
|
||||
role, so there is no anonymous answer to give.
|
||||
|
||||
### Request
|
||||
|
||||
| Parameter | Required | Meaning |
|
||||
| --- | --- | --- |
|
||||
| `page` | yes | A surface id from the closed page vocabulary — the same one `internal/definition` validates a definition's `pages:` against. Aliases (`hired`, `forge`, `new-position`) and loose spellings (`Talent Pool`) resolve to the canonical id. |
|
||||
| `query` | no | What the user has typed so far. |
|
||||
|
||||
Any other parameter is `invalid_query`. **Nothing about the caller is accepted
|
||||
here** — role, organization and user are read from the session, and a request
|
||||
that names one is refused rather than ignored.
|
||||
|
||||
An unknown `page` is `invalid_query`, with the frontend's own wording:
|
||||
`Unsupported page: {value}. Supported pages: {…}.` An absent, blank or
|
||||
too-short `query` is **not** an error — there is simply nothing to rank yet.
|
||||
|
||||
### Response
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"suggestions": [
|
||||
{ "text": "Which position has the strongest pipeline?", "intent": "position-strength" },
|
||||
{ "text": "Show hiring activity as a flow", "intent": "hiring-operations", "capability": "flow" }
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`data.suggestions` is always an array — `[]` when nothing matches, never `null`
|
||||
and never an error. There is no `meta`: the list is capped rather than paged.
|
||||
|
||||
| Field | Meaning |
|
||||
| --- | --- |
|
||||
| `text` | The question, as the user reads it. |
|
||||
| `intent` | The **frontend capability id** the panel dispatches on, verbatim from the manifests in `src/components/ai-assistant/capabilities/`. Not a backend identifier, and never invented here. |
|
||||
| `capability` | The section type to draw the answer as, from the closed `OWLIVER_CAPABILITIES` vocabulary. Present only when the query asked for one ("as a flow", "summarize"); absent otherwise. |
|
||||
|
||||
Nothing internal is exposed: no matching terms, no resource names, no scores, no
|
||||
policy detail. `text` is always catalogue wording — no part of the query is
|
||||
echoed back into a suggestion.
|
||||
|
||||
### Selection
|
||||
|
||||
Five stages, each of which only ever removes:
|
||||
|
||||
```
|
||||
the page's catalogue → permission → relevance → deduplicate → top 3
|
||||
```
|
||||
|
||||
- **Page.** Intents are keyed by surface, so a suggestion from another page
|
||||
cannot appear. The same word answers differently per page by construction:
|
||||
`pipeline` on `positions` is about which role converts, on `candidates` it is
|
||||
the funnel the applicants are in.
|
||||
- **Permission.** Each intent declares the resources it reads, and those are
|
||||
checked against the policy table in §9A — *before* ranking, so a refused
|
||||
reading is never scored. Two gates, not one: the role must be allowed the
|
||||
operation, and for an organization-wide reading the role's rows must not be
|
||||
narrowed. Talent may list job applications; talent may not be offered "which
|
||||
position has the strongest pipeline?", because their view of that resource is
|
||||
their own rows. No role list is written down here — see §9A.1.
|
||||
- **Relevance.** Deterministic keyword ranking over the intent's own terms.
|
||||
Exact token, then multi-word phrase, then prefix (so a half-typed word still
|
||||
matches), then extension. Ties break on catalogue order, so the same request
|
||||
always answers identically. A query naming only a section type ranks the
|
||||
page's readings; once it names a subject, readings that merely *support* that
|
||||
shape are dropped rather than used as padding.
|
||||
- **Deduplicate.** One suggestion per intent id, and no two with the same text.
|
||||
- **Cap.** Three. Nothing is added to reach three.
|
||||
|
||||
A query with fewer than two letters or digits after normalization returns `[]`.
|
||||
|
||||
### Normalization
|
||||
|
||||
The query is truncated to 200 characters, lower-cased, and reduced to letters,
|
||||
digits and single spaces — every other character becomes a space rather than
|
||||
being stripped, so nothing can be glued into a token that was not typed as one.
|
||||
|
||||
There is no injection surface to defend: the normalized text is compared against
|
||||
a fixed table of literals and never reaches SQL, a template, a shell or a log
|
||||
message. Hostile input is ranked like any other text, and can only ever produce
|
||||
entries the catalogue already holds.
|
||||
|
||||
### Where the catalogue comes from
|
||||
|
||||
The frontend owns the vocabulary. Owliver's capabilities are declared per page
|
||||
context in `src/components/ai-assistant/capabilities/`; `internal/owliver`
|
||||
transcribes the id and the page, and adds the two things a manifest does not
|
||||
carry — the words that mean a user is reaching for that reading, and the records
|
||||
it reads. Same pattern as `internal/definition/vocabulary.go`, and for the same
|
||||
reason: one vocabulary, named from both ends, with a test on this side that
|
||||
fails when an id here names no capability there.
|
||||
|
||||
**No table, no migration.** The suggestions are derived from definitions that
|
||||
already exist. Persistence would only be warranted if suggestions became
|
||||
admin-managed, and nothing in the product asks for that today.
|
||||
|
||||
### Not covered
|
||||
|
||||
The seven workspace and configuration surfaces — `settings`, `workspace`,
|
||||
`workspace-agents`, `workspace-skills`, `workspace-skill-configure`,
|
||||
`skill-development`, `workspace-agent-configure` — are valid pages with no
|
||||
entries. Their panel answers from the registries rather than from workforce
|
||||
records, so there is nothing to rank a typed query against. They return `[]`,
|
||||
which is the honest answer, not a validation error.
|
||||
|
||||
---
|
||||
|
||||
## 3. Request schemas
|
||||
|
||||
### 3.1 Create — `POST /{resource}`
|
||||
|
||||
Reference in New Issue
Block a user