@sporhq/spor 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,6 @@
2
2
  "name": "spor",
3
3
  "displayName": "Spor Context Compiler",
4
4
  "description": "Maintains a typed, versioned knowledge graph and compiles compact briefings from it: session-start injection, per-prompt relevance digests, capture at discovery, end-of-session distillation, decision queue.",
5
- "version": "0.8.0",
5
+ "version": "0.10.0",
6
6
  "author": { "name": "losthammer" }
7
7
  }
package/API.md CHANGED
@@ -22,7 +22,11 @@ graph's git repo. What a client sees:
22
22
  and `session: <id>` and uses `authored_via: dispatch`, while `author:` stays
23
23
  the agent's **owning person** — so the node reads "agent on behalf of person".
24
24
  These ride-along fields are token-derived too; any supplied in the payload are
25
- discarded.
25
+ discarded. The `session` is the agent's REAL run session: `spor dispatch` mints
26
+ the token **session-deferred** (it can't know the session before `claude --bg`
27
+ self-allocates it) and binds the real one post-launch via `POST /v1/agents/session`
28
+ (§3, dec-spor-dispatch-bg-session-late-bind) — so writes BEFORE the bind carry no
29
+ `session` (honest, never a phantom), and writes after trace to the actual run.
26
30
  - **Create**: `if_exists: "skip"` → id collision is reported as `skipped`
27
31
  (the distiller default); `if_exists: "error"` → id collision is a
28
32
  `conflict` error.
@@ -113,12 +117,21 @@ changes the description without a deploy.
113
117
  ### `add_edge`
114
118
 
115
119
  Micro-mutation. Input `{ "id": "<node>", "type": "<edge type>",
116
- "to": "<target>" }` — accepts canonical, alias, and inverse forms; inverse
117
- forms are flipped onto the target before writing. No revision echo is needed.
118
- Output `{ "status": "updated|skipped", "id": <node actually modified>,
119
- "revision", "warnings" }` (`skipped` = edge already present — the call is
120
- idempotent). Both nodes must exist. The tool description carries the same
121
- registry-generated vocabulary as `put_node`.
120
+ "to": "<target>", "attrs"?: { "<k>": "<v>" } }` — accepts canonical, alias, and
121
+ inverse forms; inverse forms are flipped onto the target before writing. No
122
+ revision echo is needed. Output `{ "status": "updated|skipped",
123
+ "id": <node actually modified>, "revision", "warnings" }` (`skipped` = edge
124
+ already present — the call is idempotent). Both nodes must exist. The tool
125
+ description carries the same registry-generated vocabulary as `put_node`.
126
+
127
+ The optional `attrs` carries trailing flat edge attributes — the per-assignment
128
+ `profile:` override on an `assigned → agent` edge is the motivating case. Values
129
+ round-trip only simple `[A-Za-z0-9_-]` tokens (the frontmatter edge grammar);
130
+ richer values need `put_node`, and `type`/`to` are reserved. With `attrs`, a
131
+ duplicate `(type, to)` becomes an **upsert**: same attributes → still `skipped`;
132
+ different attributes → the edge's attribute set is replaced (not merged) in
133
+ place. Omitting `attrs` never touches an existing edge's attributes, so a bare
134
+ `add_edge` is unchanged.
122
135
 
123
136
  ### `set_status`
124
137
 
@@ -297,12 +310,12 @@ endpoint is the REST twin of a core call:
297
310
  | `GET /v1/me` | `spor whoami`/`status`, onboarding | identity echo for the bearer token → `{person, name, email, bound, is_admin}`. `bound:false` means the token authenticates but maps to **no person node** (legacy/OAuth, or minted before the node existed), so routed questions and the personal queue will be empty — the client warns on it (the silent identity-degradation signal). `is_admin` reflects the `stewards→root` edge that gates the token-admin surface |
298
311
  | `GET /v1/briefing/{project}` | session-start | read the `brief-<project>` node → `{found, version, body, project_brief?, graph_status}`. The slug resolves through project-node aliases (GRAPH.md "Project identity nodes") before lookup. A BARE repo slug also rides up to its home-project grouping: the grouping's `brief-<grouping>` node returns alongside as `project_brief` (the product context spanning sibling repos), matching the shared up-resolution (dec-spor-queue-slug-resolves-to-grouping); passing the repo NODE id (`repo-<slug>`) is the escape hatch that returns only the repo brief, no `project_brief`. Optional `?fp=root:<sha>,remote:<host/path>,...` carries the repo's fingerprints: the server learns them onto the owning project node, and an unknown slug with a known fingerprint files an alias proposal in the queue |
299
312
  | `POST /v1/digest` `{query, root?, project?, min_sim?}` | prompt-context, /spor:brief | digest-mode compile → `{found, text}`; `found: false` is a successful empty result. `root` is the structural-walk twin of `query` (the two are mutually exclusive; `root` wins, an unknown id is `422`). Optional `project` is the session slug: the server scopes the compile to it — the same-project relevance boost, the grouping union, and the `always_on` norm `applies_to_*` ride-along — resolving the slug through project-node aliases/groupings inside compile (dec-spor-queue-slug-resolves-to-grouping), exactly as `/v1/queue` does. A bad slug is `422`; **omitting `project` runs the digest project-blind (byte-identical to before)**, so older clients that send only `{query}` are unaffected |
300
- | `GET /v1/nodes/{id}` | /spor:brief | `get_node` semantics; when a live inbound resolves/answers edge contradicts a still-open status the response carries `resolution`, and open gardener findings about the node ride along as `open_findings` |
313
+ | `GET /v1/nodes/{id}` | /spor:brief | `get_node` semantics; a live inbound resolves/answers edge rides along as `resolution` (carrying the resolver's `summary`/`title` and a `lagging` flag — set when it contradicts a still-open status, clear when the node is already terminal, e.g. an answered question pointing at its answer), and open gardener findings about the node ride along as `open_findings` |
301
314
  | `POST /v1/nodes` | drain-outbox, mechanical writers | `put_node` semantics, batch: `{nodes: [...], if_exists: "skip"}` (entries may be raw strings or `{node, if_exists, revision}`) → `{results: [...]}`, 207 when any entry failed |
302
- | `POST /v1/nodes/{id}/edges` `{type, to}` | scripts, mechanical writers | `add_edge` semantics (§1): normalize/flip, dedupe, append — no revision echo |
315
+ | `POST /v1/nodes/{id}/edges` `{type, to, attrs?}` | scripts, mechanical writers | `add_edge` semantics (§1): normalize/flip, dedupe, append — no revision echo. Optional `attrs` adds trailing flat edge attributes (e.g. a per-assignment `profile:` override); re-adding the same edge with different attrs upserts the set |
303
316
  | `POST /v1/nodes/{id}/status` `{status}` | scripts, mechanical writers | `set_status` semantics (§1): one-scalar update through the `transitions()` gate. Setting a work node to an in-progress status also CLAIMS it (same lease as `/claim` below) |
304
- | `POST /v1/nodes/{id}/claim` `{session?}` | `claim`/`set_status` MCP tools, `spor dispatch` | take the heartbeat-renewed lease (dec-cc-task-claim-lease): writes the durable `assigned` edge once, attributes to `$viewer` from the token (never an argument), and creates the ephemeral lease → `{ok, status, lease: {node_id, by, expires, expires_at, session, claimed_at}, edge}`. A live lease held by ANOTHER person is `409 conflict` naming the holder + expiry (re-claiming your OWN live claim just renews it). `session` scopes the heartbeat (omit to leave it person-scoped, so any of the claimer's sessions may renew — what `spor dispatch` does, since the launched agent renews from a different session) |
305
- | `POST /v1/nodes/{id}/renew` `{session?}` | post-tool heartbeat, `renew` MCP tool | bump the live lease's expiry only — no commit; the heartbeat that keeps a claim from lapsing. A lapsed/stolen lease is `409` (names the current holder). Person-scoped: any of the claimer's sessions may renew |
317
+ | `POST /v1/nodes/{id}/claim` `{session?}` | `claim`/`set_status` MCP tools, `spor dispatch` | take the heartbeat-renewed lease (dec-cc-task-claim-lease): writes the durable `assigned` edge once, attributes to `$viewer` from the token (never an argument), and creates the ephemeral lease → `{ok, status, lease: {node_id, by, expires, expires_at, session, claimed_at}, edge}`. A live lease held by ANOTHER person is `409 conflict` naming the holder + expiry (re-claiming your OWN live claim just renews it). `session` scopes the heartbeat (omit to leave it person-scoped, so any of the claimer's sessions may renew — what `spor dispatch` does at the PRE-launch claim, since `claude --bg` self-allocates the run session only at launch; dispatch then renews with the real session once it has read it from `claude agents --json`, dec-spor-dispatch-bg-session-late-bind) |
318
+ | `POST /v1/nodes/{id}/renew` `{session?}` | post-tool heartbeat, `renew` MCP tool, `spor dispatch` | bump the live lease's expiry only — no commit; the heartbeat that keeps a claim from lapsing. A lapsed/stolen lease is `409` (names the current holder). Person-scoped: any of the claimer's sessions may renew; a `session` binds the lease to that run (`spor dispatch` uses this to bind the captured `claude --bg` session post-launch) |
306
319
  | `POST /v1/nodes/{id}/release` | `release` MCP tool | drop the lease AND retire the durable `assigned` edge, returning the node to the pool. Idempotent (releasing a node you hold no lease on still succeeds, cleaning up any lingering `assigned` edge of yours); releasing a claim someone else holds is `409` naming the holder |
307
320
  | `POST /v1/nodes/{id}/commits` `{repo, sha}` | post-tool / link-commits | `link_commit`: append `repo@sha` to the node's `commits:` list (kebab-case repo slug, 7–40 lowercase hex, ≤40 commits per node); idempotent, prefix-aware dedup |
308
321
  | `GET /v1/commits/{sha}?repo=` | sessions doing git archaeology | sha → nodes lookup over the `commits:` fields (≥7 hex, abbreviated or full); each match carries `{repo, sha, id, type, title, summary, status, project}` — blame a line, get the why |
@@ -321,7 +334,8 @@ endpoint is the REST twin of a core call:
321
334
  | `DELETE /v1/admin/tokens/{hash-prefix}` | offboarding / rotation | revoke the single PAT matching the hash prefix (≥8 hex chars; an ambiguous prefix is a 409) → `{revoked, hash_prefix}`. Admin-only |
322
335
  | `GET /v1/agents` | `spor agent list` | list the agents the caller **owns** → `{agents: [{id, label, owner, spiffe, pubkey, status}], count}`; `?all=1` lists every agent (admin-only) |
323
336
  | `POST /v1/admin/agents` `{label, owner?, id?, pubkey?}` | `spor agent create`, onboarding | create a person-owned `agent` node + its `owned-by` edge (`owner` defaults to the caller's person; `id` derives from `label`) → 201 `{id, owner, spiffe, pubkey, status, revision}`. 409 dup id / 422 invalid / 403 non-admin. Admin-only, same `stewards→root` gate as `/v1/admin/tokens` |
324
- | `POST /v1/agents/{id}/token` `{session, audience?, expires?}` | `spor dispatch` | **self-serve** (NOT admin): mint a short-TTL, per-session token scoped to agent `{id}` → 201 `{token, expires_at, agent, session}`. Authorized iff the caller's person **owns** the agent (its `owned-by` edge) — else `403`; `404` unknown agent; `422` bad/missing `session`. The token carries `{agent, session}` (the person is derived from the `owned-by` edge at verify time); a write under it is stamped agent-on-behalf-of-person (§1). A caller `expires` may only shorten the default TTL, never extend it |
337
+ | `POST /v1/agents/{id}/token` `{session?, audience?, expires?}` | `spor dispatch` | **self-serve** (NOT admin): mint a short-TTL, per-session token scoped to agent `{id}` → 201 `{token, expires_at, agent, session}` (`session: null` when deferred). Authorized iff the caller's person **owns** the agent (its `owned-by` edge) — else `403`; `404` unknown agent; `422` a SUPPLIED `session` that is malformed. `session` is now **OPTIONAL** (deferred binding, dec-spor-dispatch-bg-session-late-bind): `spor dispatch` mints it deferred because `claude --bg` self-allocates the run session only at launch, then binds the real one via `POST /v1/agents/session` below. The token carries `{agent, session?}` (the person is derived from the `owned-by` edge at verify time); a write under it is stamped agent-on-behalf-of-person (§1). A caller `expires` may only shorten the default TTL, never extend it |
338
+ | `POST /v1/agents/session` `{session}` | `spor dispatch` (post-launch) | **late session binding** for a session-deferred agent token (dec-spor-dispatch-bg-session-late-bind). Authenticated by the **agent token itself** (the bearer hash identifies its own record — no agent id in the path, no ownership re-check), so only an agent-scoped token may call it (`403` otherwise). Sets that token's `session` → `200 {ok, agent, session}`. **Write-once**: idempotent on the same value (`{unchanged: true}`), `409 conflict` on a different one (a token's session is provenance, not a mutable field); `422` missing/malformed `session`. Every subsequent write under the token then stamps the bound session |
325
339
 
326
340
  Path parameters (node ids, project slugs) must match
327
341
  `^[a-z0-9][a-z0-9-]*$`. Request bodies are capped at 1MB
@@ -378,8 +392,20 @@ anything with a token.
378
392
  background agent (so the agent's own graph writes carry its identity), picking
379
393
  the machine's default agent from the `dispatch.agent` client config (set with
380
394
  `spor agent use <agent-id>`, or `SPOR_DISPATCH_AGENT`), which `spor dispatch
381
- --as <agent-id>` overrides for a single run. (Not to be confused with `spor
382
- dispatch --agent`, the unrelated `claude --agent` harness passthrough.)
395
+ --as <agent-id>` overrides for a single run. The `<agent-id>` is the agent's
396
+ `agent-`-prefixed NODE id (what `spor agent list` prints), **not** its bare
397
+ label — the token endpoint requires the prefix, so the client setters
398
+ (`spor agent use`, `--as`) reject a prefix-less id with a `did you mean
399
+ agent-…?` hint rather than persist one every dispatch would 422 on. (Not to be
400
+ confused with `spor dispatch --agent`, the unrelated `claude --agent` harness
401
+ passthrough.) The
402
+ token is minted **session-deferred** and bound to the real run session AFTER
403
+ launch (dec-spor-dispatch-bg-session-late-bind): `claude --bg` ignores
404
+ `--session-id` and self-allocates its session, so dispatch reads the real one
405
+ from `claude agents --json` and binds it via `POST /v1/agents/session` (§3) — the
406
+ one place an agent token's session is set, write-once. The session can't be
407
+ forged a-priori (it isn't known until the run exists) and can't ride the write
408
+ payload (token-derived, §1), so the binding is always the actual run.
383
409
  - **OAuth 2.1 for MCP connectors** (Cowork/claude.ai, which cannot carry a
384
410
  static bearer token): protected-resource metadata discovery (RFC 9728,
385
411
  advertised on the `/mcp` 401 via `WWW-Authenticate`), authorization-server
package/GRAPH.md CHANGED
@@ -58,7 +58,10 @@ Rules:
58
58
  omit them.
59
59
  - Edges may point at ids that don't exist yet; the compiler skips them. Don't
60
60
  delete an edge just because the target is missing — it marks a node worth
61
- creating.
61
+ creating. An edge may also carry extra flat attributes after `to:` —
62
+ `- {type: assigned, to: agent-X, profile: profile-Y}` — preserved on the edge
63
+ object (the per-assignment profile override; see "The agent orchestration
64
+ layer"). Plain `{type, to}` edges are unchanged.
62
65
  - `commits` is an optional inline list of repo-qualified git shas
63
66
  (`commits: [wf@1a2b3c4d, ...]`, kebab-case repo slug + 7–40 hex) linking
64
67
  the node to the code commits that implement it (task-cc-commit-linking).
@@ -95,6 +98,8 @@ Rules:
95
98
  | question | `question-` | a routed ask the graph could not answer (queueable; status `open`/`answered`, gated) |
96
99
  | person | `person-` | a member of the org — the identity anchor for `$viewer` binding and Tier-2 question routing (team mode; see "People, routing, and onboarding") |
97
100
  | agent | `agent-` | a person-owned automation principal — a dispatched session's durable identity, owned by a person via an `owned-by` edge; its writes attribute "agent on behalf of person" (see "Agents") |
101
+ | profile | `profile-`| a reusable runtime+capability bundle an agent runs under: `harness`, `model`, `skills`/`plugins`/`mcp`. Its runtime fields ARE the dispatch satisfiability spec; `capturable: false` (see "The agent orchestration layer") |
102
+ | routine | `routine-`| owner-scoped trigger→action automation (`owned-by` a person): declarative `when → do` rules over graph events that dispatch only the owner's agents, AND-ed with org policy; `capturable: false` (see "The agent orchestration layer") |
98
103
  | capture-pending | `cap-` | raw captured text that fit no schema; filed by the server for later triage (QUEUE.md §2.3); born status-less, closed only as `merged` (content now in proper node(s)) or `rejected` (no durable fact) — a `transitions()` gate rejects other statuses at write time |
99
104
  | finding | `find-` | a gardener observation about another node, filed as a queue item (QUEUE.md §6) |
100
105
  | repo | `repo-` | durable git-repo identity: slug aliases + repo fingerprints; heals renames at read time (below) |
@@ -606,6 +611,112 @@ edges:
606
611
  mirroring `person`, `repo`, and `workflow-run`. (So the distiller's emit
607
612
  vocabulary deliberately omits `agent`/`owned-by`.)
608
613
 
614
+ ## The agent orchestration layer
615
+
616
+ The layer ABOVE agent identity (dec-spor-agent-orchestration-layer): how work is
617
+ routed to agents, and how per-person automation fires on graph events. The node
618
+ model is `person ──owns──▶ agent ──uses-profile──▶ profile`, with owner-scoped
619
+ `routine` nodes driving automation. It composes with — never bypasses — the org
620
+ policy layer. The schemas ship in the seed pack; the dispatch matcher, the
621
+ routine engine, and the remote fleet scheduler are deferred
622
+ (task-spor-dispatch-capabilities-satisfiability).
623
+
624
+ ### profile — the reusable runtime+capability bundle
625
+
626
+ A `profile` node (prefix `profile-`) is "the HOW" an agent dispatches under,
627
+ factored out of the agent node so a toolset is reusable across agents and people.
628
+
629
+ ```markdown
630
+ ---
631
+ id: profile-docs-writer
632
+ type: profile
633
+ title: Docs-writer profile
634
+ summary: Claude-code on Opus with the writing + spor skills and the spor MCP server.
635
+ harness: claude-code
636
+ model: opus
637
+ skills: [writing, brief]
638
+ plugins: [spor]
639
+ mcp: [spor]
640
+ status: active
641
+ date: 2026-06-18
642
+ ---
643
+ ```
644
+
645
+ - `harness:` (`claude-code` | `codex` | `opencode` | …) selects the launcher
646
+ (dec-cc-portable-core-adapters: claude-code → `claude --bg`, others → their
647
+ CLIs); `model:` → the harness `--model`; `skills`/`plugins` are preloaded;
648
+ `mcp` is merged into the strict `--mcp-config` dispatch writes, so the agent's
649
+ toolset is exactly the profile plus the agent-spor server, nothing ambient
650
+ (dec-spor-session-identity-active-record).
651
+ - **The runtime fields ARE the satisfiability spec** — there is no separate
652
+ requirements block (dec-spor-machine-profile-satisfiability). A machine
653
+ declares ATOMIC capabilities in a machine-local `dispatch.capabilities` map
654
+ (built like `dispatch.repos`, never committed), and `satisfies(machine,
655
+ profile)` checks `profile.harness ∈ machine.harnesses ∧ profile.mcp ⊆
656
+ machine.reachable_mcp ∧ profile.skills ⊆ machine.skills ∧ profile.plugins ⊆
657
+ machine.plugins ∧ profile ∉ machine.deny`. No satisfying machine → dispatch
658
+ fails soft and LOUD, leaves the assignment intact, NEVER substitutes a
659
+ different profile. Forward-compatible with the deferred remote fleet scheduler
660
+ (each agent publishes its capabilities; same vocabulary).
661
+ - **Reusable + both-scoped, with override.** Profiles are PERSONAL and
662
+ ORG-PUBLISHED (a curated, vetted toolset), with personal override. Org-published
663
+ profiles are where this meets policy: a policy can require that work of a risk
664
+ class go to an agent whose profile is org-approved (curated-toolset-as-governance).
665
+ - `status:` is declarative (`active` default); `capturable: false` (created
666
+ deliberately, never distilled).
667
+
668
+ ### routine — owner-scoped trigger→action automation
669
+
670
+ A `routine` node (prefix `routine-`), `owned-by` a person, holds declarative
671
+ `when → do` rules. Triggers are graph events (a status change — e.g. to a
672
+ resolving state, or to `done` — or an edge like `reviewed-by`/
673
+ `changes-requested-by`) with `where:` filters; actions are the bounded verb set
674
+ `create-node` / `assign` (to a SPECIFIC agent, optionally with a `profile:`
675
+ override) / `dispatch` / `set-status` / `reassign` / `escalate`. Because the
676
+ regex frontmatter parser is flat, an instance carries its `rules` as a fenced
677
+ ` ```json ` block in its BODY, parsed by the deferred routine engine.
678
+
679
+ Two invariants (dec-spor-agent-orchestration-layer): (1) **only the owner's
680
+ agents are ever dispatched** — the `person → routine → agent` RFC 8693 act-chain
681
+ is the audit trail; (2) **personal routines accelerate, org policy gates — they
682
+ AND, never bypass** — agent-on-behalf-of-X counts as X for the self-approval ban
683
+ and the definition-of-done quorum, so an owner's reviewer-agent cannot
684
+ auto-approve their implementer-agent's work past the org bar. Per
685
+ dec-spor-orchestration-routine-requires-threads (thread 1) the bounded
686
+ declarative register ships FIRST; attached sandboxed code is a later
687
+ schema-gated escape hatch. The routine ENGINE and its vetting (dry-run, mandatory
688
+ retry budget, self-approval-floor activation review, fire-time org-policy gate —
689
+ thread 2) are deferred, so this seed schema is declarative and UNGATED;
690
+ `capturable: false`.
691
+
692
+ ### Routing is explicit assignment; the profile cascade
693
+
694
+ Routing is EXPLICIT `assigned` to a SPECIFIC target, not an eligibility match:
695
+ `assigned → person` is human work (the default human pool); `assigned → agent-X`
696
+ makes the task a dispatch candidate (X's profile says how and what it may touch).
697
+ An agent-targeted `assigned` edge may carry an optional `profile:` attribute —
698
+ the durable per-assignment override (`{type: assigned, to: agent-X, profile:
699
+ profile-Y}`, thread 3). Profile precedence, explicit wins: `--profile` dispatch
700
+ flag > assignment-edge `profile:` attribute > the agent's default `uses-profile`
701
+ edge.
702
+
703
+ ### The `requires:` risk-class register
704
+
705
+ `requires:` is a flat list on a WORK node naming the risk/permission classes the
706
+ work may touch (`requires: [shell, prod-creds]`). It is a **registry-declared
707
+ extensible enum** — a `type: schema` node with `kind: register` and `register:
708
+ requires` (seed: `schema-requires`), so an org grows the vocabulary by editing a
709
+ schema node, never a code change. The kernel exposes it as a partition
710
+ (`graph.registry.requiresClasses()`); it stays policy-free. The register is
711
+ **DISTINCT from machine-satisfiability**: satisfiability asks "can this box LAUNCH
712
+ the profile"; `requires:` asks "what may this work touch", validated against the
713
+ assigned profile (a task's `requires:` must be ⊆ the profile's granted classes,
714
+ else warn/refuse) and gated by org policy via the same governs-traversal the
715
+ definition-of-done quorum uses (dec-spor-orchestration-routine-requires-threads
716
+ thread 4). The seed set is small — `shell`, `prod-creds`, `browser`, `network`,
717
+ `human`, `filesystem-write`, `paid-api`. `human` is unsatisfiable by any agent:
718
+ assign that work to a person.
719
+
609
720
  ## Edge types and traversal weights
610
721
 
611
722
  | edge | weight | meaning |
@@ -618,7 +729,7 @@ edges:
618
729
  | `resolves` | 0.9 | this node fixes/closes the target |
619
730
  | `blocks` | 0.7 | target cannot proceed until this node does |
620
731
  | `answers` | 0.7 | this node answers that question (inverse `answered-by`); pulls the answer through the asker's next compile |
621
- | `assigned` | 0.5 | work is assigned to this person |
732
+ | `assigned` | 0.5 | work is assigned to this person OR agent (the explicit-routing edge; an agent target may carry a `profile:` per-assignment override) |
622
733
  | `reviewed-by` | 0.5 | this person reviewed and approved the node — counts toward a policy quorum |
623
734
  | `changes-requested-by` | 0.5 | this person reviewed the node and requested changes — not an approval |
624
735
  | `relates-to` | 0.5 | weak association |
@@ -626,6 +737,7 @@ edges:
626
737
  | `stewards` | 0.4 | this person stewards an area/spec/norm — the Tier-2 question-routing key |
627
738
  | `grouped-under` | 0.3 | this repo's home project grouping (inverse `groups`); structural membership, not work dependency |
628
739
  | `owned-by` | 0.3 | this agent is owned by that person (inverse `owns`); structural identity binding, not work dependency |
740
+ | `uses-profile` | 0.3 | this agent's default profile (the runtime+capability bundle it dispatches under); structural config binding, overridable per assignment/dispatch |
629
741
  | `routed-to` | 0.3 | a question routed to this person for answering |
630
742
  | `review-requested` | 0.3 | a review of this node is requested of this person (pending) — surfaces in their queue |
631
743
  | `compiled-for` | — | briefing → its task/query (provenance only) |
package/QUEUE.md CHANGED
@@ -142,6 +142,15 @@ is the default and graph-resident schema nodes override/extend it. The
142
142
  `schema` node type itself is recognized natively by the core — no
143
143
  schema-for-schemas regress.
144
144
 
145
+ Beyond `node-schema`/`edge-schema`, a schema node may carry a non-type `kind`:
146
+ `policy` (the org-defined policy layer, §below), `queue-policy` (the ranking
147
+ override singleton), and `register` — a registry-declared extensible **enum**
148
+ the kernel exposes as a partition (`graph.registry.register(name)` /
149
+ `requiresClasses()`) instead of a hardcoded table. The seed `requires` register
150
+ (`schema-requires`, the work-node risk/permission axis) is the first; an org
151
+ grows the vocabulary by writing a resident `kind: register` schema with the same
152
+ `register:` name (graph beats seed, higher CalVer wins — like every other kind).
153
+
145
154
  Resolution is **graph beats seed wholesale, regardless of version**: a
146
155
  resident override replaces the seed entry for that type entirely, so a seed
147
156
  behavior change (a new `transitions()` gate, a default, a prefix) does *not*