Claim ownership of the browser-facing approver UI that approval-engine deliberately does not contain. approval-engine's INTENT names an approvals inbox under Non-Goals, and docs/keycape-service-registrations.md records that the human approver client's client_id and callback URI "must come from its owner once it exists" — leaving key-cape's KEY-WP-0013-T02 blocked on an unassigned component. - INTENT.md: Decision Memo concept, the binding/awareness split and the two hashes, ownership and non-ownership against the named estate repositories, and a provisional PEP-shaped layer placement flagged for a gate-house ruling rather than asserted. - GOAL.md: Stage 1 is the L3 approval approver surface — the narrowest real consumer with a live blocking dependency — plus the written answer to who owns the approver UI. - workplans/INFD-WP-0001: founding documents, the gate-house layer/ownership ruling, the four specs (PRD, UseCaseCatalog, ArchitectureBlueprint, EvidenceModel), schema and canonicalizer promotion out of history/ with the isolation vectors under test, the key-cape client registration, and a walking skeleton that includes return and discuss. history/ is preserved unmodified as provenance. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V3W1dQG7GFFM9d94jFx7iR Assistant: claude-code Assistant-Model: opus Assistant-Process: 1565372@bnt-lap001 Assistant-Session: 16bb2f25-b34c-49ef-8e94-5fec3567a568
149 lines
7.2 KiB
Markdown
149 lines
7.2 KiB
Markdown
# Decision Memo object schema v0.2
|
|
|
|
Machine-readable schema: `decision-memo.schema.json`
|
|
Worked example: `decision-memo.example.json`
|
|
|
|
The Decision Memo is one object at every depth (L0 login banner through L5 multi-party instrument). Depth changes which fields are populated, not which types exist.
|
|
|
|
## 1. Object graph
|
|
|
|
```
|
|
DecisionMemo
|
|
├── requester / subject / parties[] identity + mandate
|
|
├── question + requested_act + binding_level what is being asked
|
|
├── brief + consequences
|
|
├── identity_context.binding PRE-SIGN identity + scope (in view_hash)
|
|
├── identity_context.awareness hats, last-used, other tenants (awareness_hash)
|
|
├── session POST-SIGN live hat inside the bound scope
|
|
├── packet[] files, hashed
|
|
├── highlights[] pointers into packet
|
|
├── route.steps[] Laufweg / Zeichnungsleiste
|
|
├── thread[] discussion stays on the object
|
|
├── presentations[] what each actor was shown
|
|
├── dispositions[] comment | return | forward | accept…
|
|
├── relations[] supersedes / relates_to
|
|
└── evidence hash-chained event log + optional seal
|
|
```
|
|
|
|
## 2. Lifecycle
|
|
|
|
```
|
|
draft
|
|
→ submitted
|
|
→ circulating / awaiting_actor / in_discussion
|
|
→ returned ──(new version)──→ submitted
|
|
→ completed (outcome set)
|
|
→ withdrawn | expired
|
|
completed ─(new memo)→ superseded
|
|
```
|
|
|
|
Rules:
|
|
|
|
- Edits in `draft` bump `version` without a new `id`.
|
|
- After `submitted`, content edits that change `brief`, `packet`, or `highlights` **must** bump `version` and invalidate unused presentations.
|
|
- `return` sets status `returned`, does not mutate the sealed packet in place. Requester opens version N+1, which `supersedes` the circulation of N.
|
|
- `forward` replaces the current step assignee. `escalate` adds or replaces a step of higher competence.
|
|
- Terminal statuses: `completed`, `withdrawn`, `expired`, `superseded`.
|
|
- `outcome` is only set when `status=completed` (or timed-out/cancelled mappings).
|
|
|
|
## 3. Invariants
|
|
|
|
I1. **One question.** `question` is a single present-tense sentence. If you need two questions, you need two memos.
|
|
|
|
I2. **Presentation binds the act.** Every `disposition` with verb in `{acknowledge, accept, decline, abstain, recuse, sign}` MUST reference a `presentation_id` whose `memo_version` equals the memo version at act time.
|
|
|
|
I3. **Dynamic linking.** Binding acts at `aes` or higher MUST set `signature.presentation_bound=true` and include `presentation.view_hash` in the signed attributes.
|
|
|
|
I4. **Required highlights.** If any highlight has `required_ack=true`, a binding act is invalid unless those ids are in `presentation.acked_highlight_ids`.
|
|
|
|
I5. **Mandate fit.** The actor on a `schlusszeichnung` / `sign` step MUST carry a `mandate` whose scope covers `requested_act` and any `legal` / value constraints.
|
|
|
|
I6. **Packet integrity.** `packet[].hash` is content-addressed. Replacing bytes is a new `item.id` (or a new item version + `replaces`).
|
|
|
|
I7. **Evidence is append-only.** `evidence.events[].event_hash = H(seq || type || at || payload_hash || prev_hash)`. `head_hash` is the last `event_hash`.
|
|
|
|
I8. **Agents may draft, not bind.** `party.kind=agent` may create highlights and brief text. Agents MUST NOT emit binding verbs.
|
|
|
|
I9. **Return is data.** `verb=return` requires `return.reasons[]`. Empty “please fix” is invalid.
|
|
|
|
I10. **Whole instrument vs excerpt.** Highlights never narrow the legal object. `legal` and the primary packet item define what is bound; highlights are attention, not scope.
|
|
|
|
I11. **Pre-sign vs post-sign.** `identity_context.binding` is in `view_hash`. `identity_context.awareness` is only in `awareness_hash`. Binding verbs sign the former. `configure` mutates `session` after bind and never rewrites `binding`.
|
|
|
|
I12. **Hats are not scopes.** A hat (`elevates=false`) is a perspective inside an already bound scope. A scope with `requires_new_bind=true` (other tenant, other identity, elevating hat) is a new memo.
|
|
|
|
I13. **Defaults are awareness.** `proposed_hat_source` of `last_used` or `system_default` must be shown and must not be silently copied into `view_hash`.
|
|
|
|
## 4. Disposition verbs
|
|
|
|
| Verb | Changes route? | Binding? | Required payload |
|
|
|---|---|---|---|
|
|
| comment | no | no | body |
|
|
| discuss | may set `in_discussion` | no | body + optional transfer.to for requester |
|
|
| return | yes → requester | no | return.reasons |
|
|
| forward | yes → peer assignee | no | transfer.to |
|
|
| escalate | yes → higher mandate | no | transfer.to + reason |
|
|
| acknowledge | completes inform/review step | weak | presentation_id |
|
|
| accept | may complete memo | yes | presentation_id + optional signature |
|
|
| decline | may complete memo | yes | presentation_id + body |
|
|
| abstain / recuse | completes step for actor | yes | presentation_id + reason |
|
|
| withdraw | terminal | requester only | body |
|
|
| stamp | no | system/org seal | signature on listed items |
|
|
|
|
## 5. Canonical `view_hash`
|
|
|
|
Serialize, UTF-8, JCS (RFC 8785), then hash:
|
|
|
|
```json
|
|
{
|
|
"memo_id": "...",
|
|
"memo_version": 2,
|
|
"question": "...",
|
|
"requested_act": "accept",
|
|
"binding_level": "aes",
|
|
"brief": "...",
|
|
"packet": [{"item_id":"...","hash":"sha256:..."}],
|
|
"highlights": [{"id":"...","item_id":"...","locator":{...},"required_ack":true}],
|
|
"ui_release": "informed-decision@0.3.1",
|
|
"locale": "en"
|
|
}
|
|
```
|
|
|
|
That document is what “informed” means in the evidence bundle.
|
|
|
|
## 6. Evidence event minimum set
|
|
|
|
For a completed accept:
|
|
|
|
1. `memo.created`
|
|
2. `memo.submitted`
|
|
3. `packet.attached` (per item)
|
|
4. `highlight.added` (per required highlight)
|
|
5. `route.started`
|
|
6. `identity.authenticated`
|
|
7. `presentation.opened`
|
|
8. `presentation.highlight_acked`
|
|
9. `disposition.recorded` (accept)
|
|
10. `signature.created` (if aes/qes)
|
|
11. `timestamp.qualified` (if qes / long retention)
|
|
12. `file.stamped` (per `is_to_be_stamped`)
|
|
13. `bundle.sealed`
|
|
|
|
## 7. Field usage by depth
|
|
|
|
| Field | L0 login | L1 elevate | L2 ADR | L3 approval | L4 contract | L5 treaty |
|
|
|---|---|---|---|---|---|---|
|
|
| identity_context | required | required | optional | optional | rare | rare |
|
|
| packet | optional | ticket + change | ADR + links | memo + annexes | contract + annexes | instruments + translations |
|
|
| highlights | optional | blast-radius | rollback / cost | risk spans | clauses | reservations |
|
|
| binding_level | acknowledgment / organizational | organizational | organizational / aes | organizational / aes | aes / qes | qes per party |
|
|
| route | one step | requester → approver | review → accept | matrix | sequential/parallel signers | hybrid + deposit |
|
|
| legal.form_requirement | none | none | none | none / textform | textform / schriftform | varies by party |
|
|
|
|
## 8. What is deliberately not in v0.1
|
|
|
|
- Full mandate-graph resolution (referenced by `mandate.policy_ref`, not modelled here)
|
|
- Payment / consideration objects
|
|
- Translation alignment and reservation semantics for L5
|
|
- Raw PII in evidence (use `ip_hash`, identifier schemes)
|
|
- UI widget tree — only `ui_release` + canonical view
|