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
7.2 KiB
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
draftbumpversionwithout a newid. - After
submitted, content edits that changebrief,packet, orhighlightsmust bumpversionand invalidate unused presentations. returnsets statusreturned, does not mutate the sealed packet in place. Requester opens version N+1, whichsupersedesthe circulation of N.forwardreplaces the current step assignee.escalateadds or replaces a step of higher competence.- Terminal statuses:
completed,withdrawn,expired,superseded. outcomeis only set whenstatus=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:
{
"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:
memo.createdmemo.submittedpacket.attached(per item)highlight.added(per required highlight)route.startedidentity.authenticatedpresentation.openedpresentation.highlight_ackeddisposition.recorded(accept)signature.created(if aes/qes)timestamp.qualified(if qes / long retention)file.stamped(peris_to_be_stamped)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