design rev 3: GLM round-2 findings
ci/woodpecker/pr/ci Pipeline was successful

shared replay mode refused (seed-only rule, contract 3 SS4.3/SS7 item 4);
concurrency loser semantics and scope-mismatch collision added to rule 5
and witness 5; query-side correlation envelope specified; no-oracle
indistinguishability probe added to witness 9; fence authorization
scope pinned; SHARED-CONTRACT SS5.3 citation fixed, fleet-side ASKS
reference removed.
This commit is contained in:
fred
2026-08-29 20:00:54 -05:00
parent 7bc69286d1
commit d18ce04959
@@ -112,10 +112,10 @@ One command, one query. Module: `apps/gateway/src/enrollment/`
Request DTO (shared types package, class-validator at the boundary):
| Field | Type | Rule |
| ---------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ---------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `harness` | string | syntactically invalid (empty/malformed) → `validation_failed`; well-formed but not in the harness registry → `precondition_failed` |
| `correlationId` | string (uuid) | optional; generated when absent (contract 5 §4.3); carried into audit events and returned in the result |
| `replayMode` | 'actor-bound' \| 'shared' | optional, default `actor-bound` (contract 3 §4.3) |
| `replayMode` | 'actor-bound' | optional, default `actor-bound`. `shared` is seed-only (contract 3 §4.3 binds it to the §3.4 canonical seed key set and "no other operation can carry a shared declaration"; §7 item 4 closes it); a `shared` declaration here is refused `validation_failed`, executes nothing, and records no fence row |
| `name` | string | non-empty, trimmed, ≤ 200 chars |
| `persona` | string \| null | optional; stored as the agent's system prompt |
| `model` | string | non-empty (provider-qualified model id) |
@@ -149,21 +149,35 @@ Rules:
authorization scope, a digest of the canonicalized request payload
(the digest input EXCLUDES the credential value — it covers
provider + credentialMode, never plaintext), the declared replay
mode (`actor-bound` default / `shared`), and a reference to the
committed outcome (the agent id). Fence uniqueness is the pair
(operation identifier, key). **Replay:** a submission whose
mode (always `actor-bound` for this family — the `shared` refusal
in the table above means no shared fence row can exist here; the
column is kept for envelope-shape fidelity and mode-mismatch
collision checks), and a reference to the committed outcome (the
agent id). The recorded **authorization scope** for this family is
pinned to the acting principal's platform-user scope (v1
authorization is grant-free per rule 4, so the scope is the
authenticated-user identity domain — recorded so the §4.3
scope-equality check has a defined value). Fence uniqueness is the
pair (operation identifier, key). **Replay:** a submission whose
(operation, key) is recorded is first authorized exactly as a fresh
submission; then replay-mode, scope, and digest equality are
checked; then **target-result authorization** — the submitter must
hold, at replay time, read authority on the referenced agent row
under §3.2's rule (owner or admin) — in every mode, with
recorded-actor equality as the additional `actor-bound` condition.
A passing replay executes nothing, returns the recorded outcome,
and appends a replay access event (non-mutation audit class:
accessing principal, current correlation id, fence-row reference).
Any equality or authorization failure refuses with the single
bounded `conflict` shape — constant, identifying no record —
preserving the no-existence-oracle rule.
checked (a mismatch on any — including scope — is a collision);
then **target-result authorization** — the submitter must hold, at
replay time, read authority on the referenced agent row under
§3.2's rule (owner or admin) — plus recorded-actor equality
(`actor-bound`). A passing replay executes nothing, returns the
recorded outcome, and appends a replay access event (non-mutation
audit class: accessing principal, current correlation id,
fence-row reference). Any equality or authorization failure refuses
with the single bounded `conflict` shape — constant, identifying no
record — preserving the no-existence-oracle rule. **Concurrency
(contract 3 §4.3's rule, ratified via §7 item 4):** two submissions
with the same (operation, key) serialize on the fence's unique
constraint — exactly one executes; the loser waits for the winner's
transaction, and is then handled as a replay if it committed
(through the full replay path above) or executes afresh if it
aborted. A unique-violation race never surfaces as an unhandled
internal fault.
6. **Audit + outbox, same transaction.** Insert into `agents` +
sealed credential write (intake mode) + fence row + semantic audit
event (`agent.enrolled`: actor, agent id, harness, provider, name,
@@ -184,6 +198,14 @@ By agent id; actor must be the owner (or admin). Unauthorized and
missing fold to the same `not_found` wire shape (contract 2
no-existence-oracle rule, applied family-wide for uniformity).
The query carries the same non-state envelope as the mutation
(contract 5 §4.3; contract 3's envelope reconciliation confirms closed
query responses carry it): typed request DTO with an optional
`correlationId` (generated when absent) and a typed result —
`found { agent, correlationId }` | `not_found` (the folded shape,
also carrying the correlation id). Queries take no idempotency key
(the fence binds mutations).
### 3.3 Error enum (closed, §4.2)
`validation_failed` 400 · `authentication_failed` 401 ·
@@ -227,8 +249,9 @@ including after the credential write), fence uniqueness on
(operation, key).
Sequencing: additive DDL via the same migration path as 00180020
(hierarchy). The KBN-101 §5.3 gate binds KBN-100 kanban DDL, not this
lane; if the pending ASKS-5 ruling changes migration mechanics
(hierarchy). The docs/native-kanban-sot/SHARED-CONTRACT.md §5.3 DDL
gate binds the kanban lane's audit/proposal DDL, not this lane; if a
pending operator ruling on migration sequencing changes mechanics
lane-wide, re-check before generating 0021.
## 5. Witnesses the implementation slice must ship
@@ -246,10 +269,15 @@ lane-wide, re-check before generating 0021.
5. Idempotency (contract 3 §4.3 set): actor-bound replay returns the
recorded outcome and executes nothing (no new agent/audit/outbox
mutation rows; a replay access event is appended); payload-digest
mismatch, replay-mode mismatch, and different-actor actor-bound
replay each refuse with the single bounded `conflict` shape; a
replay is re-authorized fresh (a submitter whose authorization was
revoked since the original is refused, not replayed).
mismatch, replay-mode mismatch, scope mismatch, and different-actor
actor-bound replay each refuse with the single bounded `conflict`
shape; a replay is re-authorized fresh (a submitter whose
authorization was revoked since the original is refused, not
replayed); a `shared` declaration on `agent.enroll` is refused
`validation_failed` with nothing executed and no fence row
recorded (seed-only rule); two concurrent same-(operation, key)
submissions produce exactly one mutation, the loser resolving
through the replay path (no unhandled unique-violation fault).
6. Same-tx atomicity fault injection (agent / credential write / fence
/ audit / outbox), including a failure injected after the intake
credential write commits its statement — everything rolls back, no
@@ -260,8 +288,11 @@ lane-wide, re-check before generating 0021.
8. `is_system` injection attempt is rejected by DTO validation.
9. Correlation-id witness (contract 5 §6.3): a correlation id submitted
on `agent.enroll` appears in its audit event(s) and in the result;
the §6.3 static companions (no `any`-typed boundary pass-through;
single audit emitter) apply.
the same holds for `agent.enrollment.get`'s result; the §6.3 static
companions (no `any`-typed boundary pass-through; single audit
emitter) apply. §6.3's no-existence-oracle probe: an unauthorized
`agent.enrollment.get` of an existing agent and a get of a
nonexistent id return indistinguishable results.
10. CLI-parity witness (contract 5 §6.4): a CLI smoke invocation of
`agent.enroll` and `agent.enrollment.get` against the Gateway
succeeds with the same typed results the web client receives. The