DUNIN7 · LOOMWORKS · RECORD
record.dunin7.com
Status Current
Path change-requests/cr-2026-188-loomworks-b47-credential-issuance-v0_2.md

DUNIN7-M4 — INFRASTRUCTURE CHANGE REQUEST

CR-2026-188 — B-47: contributor credential issuance screen (v0.2)

Version. 0.2 · Date. 2026-08-08 · Author. Claude.ai (v0.1 drafting) / Claude Code (v0.2 amendment) / Marvin Percival (approving). Supersedes. v0.1 at record 43841fe, which stands as a sibling. Target. /Users/dunin7/loomworks, branch main. Surface only — Step 1 found no engine gap. Baseline. Surface 0dd7dd9, engine ad83960.

What changed from v0.1. Step 1's read resolved a contradiction in v0.1's own text, and found the one-time-reveal guarantee to be stronger than v0.1 assumed. §2's halt condition did not trigger.


1. Correction: §4 excluded expiry management, not the expiry field

v0.1's §4 put "expiry" out of scope. The issuance endpoint's request model requires expires_at. Those cannot both hold — a form cannot omit a required field.

The intent, clarified (Operator, 2026-08-08): "§4 was meant to exclude expiry MANAGEMENT (rotation policy, renewal, notifications), not the required field on the issuance form."

So the form collects expires_at as a dated field with a sensible default the Operator can change. Out of scope remains: rotation policy, renewal flows, expiry-driven notification, and usage auditing.

Why collected rather than defaulted invisibly. A credential granting unmoderated write access should have its lifetime visible at the moment of issuing. The alternative — hardcoding a default in the surface — was rejected on its own terms: it buries a policy decision in a form where the Operator cannot see what they are agreeing to. Defaulting it server-side was also rejected: it would require an engine change on a CR whose target is the surface.

2. The one-time reveal is enforced structurally, not by convention

Stronger than v0.1 assumed. The guarantee does not depend on the screen behaving well:

There is nothing to re-display. The credential is unreachable after issuance because the response model that would carry it is not used anywhere else — a boundary in the type system rather than a rule the UI is trusted to follow.

Gate item 1 therefore changes shape. It is no longer "prove the surface shows it once"; it is "prove the surface does not reintroduce a path back" — including not caching the issuance response in any store, context, history entry, or query string where it could be re-read. That is what the test must demonstrate.

3. Step 1's findings

| | Route | Notes | |---|---|---| | Issue | POST /engagements/{eid}/contribution-credentials | 201; body recipient_label (optional) + expires_at (required) | | List | GET /engagements/{eid}/contribution-credentials | never carries a token | | Revoke | POST /engagements/{eid}/contribution-credentials/{cid}/revoke | exists — §2's halt did not trigger |

All three are Operator-only through _require_operator. No engine work is needed, so this CR is surface-only as v0.1 anticipated.

The list returns status, recipient_label, expires_at, created_at, claimed_by_person_id, claimed_at, revoked_at — enough to carry a revoke control and to show whether a credential has been claimed or already revoked.

4. Everything else stands

v0.1's §3 (the reveal, the revoke, Operator authority), §4's remaining exclusions, and §6's other gate items are unchanged.

5. Revised build steps

| Step | What | Mode | State | |---|---|---|---| | 0 | Pre-flight | Auto | done | | 1 | Read per §2; halt if no revoke | Auto, conditional halt | done — no halt | | 2 | One-time-reveal screen + tests | Auto | pending | | 3 | Revoke control + confirmation + tests | Auto | pending | | 4 | Test that no path back exists — reload, navigation, list, history, and no cached response | Auto | pending | | 5 | Surface sweep | Auto | pending | | A | Checkpoint | Checkpoint | pending | | 6 | Tag cr-2026-188-b47-credential-issuance. Push. | Auto | pending |


DUNIN7 — Done In Seven LLC — Miami, Florida CR-2026-188 — B-47 credential issuance — v0.2 — 2026-08-08