roleAssignments) {
public AssetView {
@@ -112,4 +113,11 @@ public record RoleAssignment(
UUID assignedByUserId,
Instant projectedAt) {
}
+
+ public record OwnershipHealth(
+ boolean ownerPresent,
+ boolean backupOwnerPresent,
+ boolean orphaned,
+ boolean continuityAtRisk) {
+ }
}
diff --git a/demo/fixtures/asset-registry/README.md b/demo/fixtures/asset-registry/README.md
new file mode 100644
index 00000000..79fa782f
--- /dev/null
+++ b/demo/fixtures/asset-registry/README.md
@@ -0,0 +1,28 @@
+# L1 Support Asset Registry Golden POC
+
+This synthetic fixture proves the browser-native, governed reuse path without
+customer or employee data.
+
+Stable coordinates:
+
+- Knowledge: `support.sla-and-escalation@1`
+- Work Instruction: `support.classify-and-respond@1.0.0`
+- Prompt Template: `support.triage-customer-ticket@1.0.0`
+- Capability Pack: `support.l1-onboarding@1.0.0`
+- Evaluation rubric: `support.triage-quality@1`
+
+Files:
+
+- `support-sla-and-escalation.md` is the permission-aware grounding source.
+- `prompt-template.json` is the released Prompt payload with eight bounded
+ evaluation cases.
+- `work-instruction.json` is the released task procedure.
+- `capability-pack-template.json` is resolved with exact release UUIDs by the
+ golden integration test.
+- `quality-checklist.json` is the human verification checklist.
+- `mock-tickets.json` fixes expected classification, SLA, escalation, and
+ citation behavior.
+- `success-metrics.json` defines the POC metric formulas and thresholds.
+
+The fixture intentionally contains no executable Skill, Tool, Agent, public
+marketplace metadata, Screenpipe event, or MCP mutation.
diff --git a/demo/fixtures/asset-registry/capability-pack-template.json b/demo/fixtures/asset-registry/capability-pack-template.json
new file mode 100644
index 00000000..ac35b4e6
--- /dev/null
+++ b/demo/fixtures/asset-registry/capability-pack-template.json
@@ -0,0 +1,36 @@
+{
+ "purpose": "ROLE_ONBOARDING",
+ "audience": "L1 support agent",
+ "prerequisites": [
+ "Active support account",
+ "Access to the Support Knowledge Space"
+ ],
+ "expectedOutcome": "The agent can classify, ground, draft, verify, and record a correct first ticket",
+ "items": [
+ {
+ "key": "instruction",
+ "required": true,
+ "kind": "REGISTRY_RELEASE",
+ "assetId": "${WORK_INSTRUCTION_ASSET_ID}",
+ "releaseId": "${WORK_INSTRUCTION_RELEASE_ID}",
+ "knowledgeAssetId": null,
+ "knowledgeVersionId": null
+ },
+ {
+ "key": "prompt",
+ "required": true,
+ "kind": "REGISTRY_RELEASE",
+ "assetId": "${PROMPT_ASSET_ID}",
+ "releaseId": "${PROMPT_RELEASE_ID}",
+ "knowledgeAssetId": null,
+ "knowledgeVersionId": null
+ }
+ ],
+ "completionCriteria": [
+ "Both required released items are complete",
+ "One mock ticket passes support.triage-quality@1",
+ "The execution trace pins the Prompt release and grounding citation"
+ ],
+ "reviewDate": "2026-12-31",
+ "owner": "Support operations"
+}
diff --git a/demo/fixtures/asset-registry/mock-tickets.json b/demo/fixtures/asset-registry/mock-tickets.json
new file mode 100644
index 00000000..35f6c954
--- /dev/null
+++ b/demo/fixtures/asset-registry/mock-tickets.json
@@ -0,0 +1,10 @@
+[
+ {"id":"TICKET-001","scenario":"billing question","text":"Why is my monthly invoice higher than last month?","category":"billing","slaTier":"P2","escalate":false,"allowedCitations":["support.sla-and-escalation@1"],"rubricPass":true},
+ {"id":"TICKET-002","scenario":"password reset","text":"I cannot sign in and need a password reset.","category":"access","slaTier":"P2","escalate":false,"allowedCitations":["support.sla-and-escalation@1"],"rubricPass":true},
+ {"id":"TICKET-003","scenario":"degraded service","text":"Reports take ten minutes to load for every user.","category":"degraded_service","slaTier":"P1","escalate":true,"allowedCitations":["support.sla-and-escalation@1"],"rubricPass":true},
+ {"id":"TICKET-004","scenario":"confirmed outage","text":"The production API is unavailable in all regions.","category":"outage","slaTier":"P0","escalate":true,"allowedCitations":["support.sla-and-escalation@1"],"rubricPass":true},
+ {"id":"TICKET-005","scenario":"suspected security issue","text":"I saw an unknown login and possible account takeover.","category":"security","slaTier":"P0","escalate":true,"allowedCitations":["support.sla-and-escalation@1"],"rubricPass":true},
+ {"id":"TICKET-006","scenario":"data deletion request","text":"Please permanently delete my account data.","category":"privacy","slaTier":"P1","escalate":true,"allowedCitations":["support.sla-and-escalation@1"],"rubricPass":true},
+ {"id":"TICKET-007","scenario":"duplicate ticket","text":"This repeats my open ticket number 4312.","category":"duplicate","slaTier":"P2","escalate":false,"allowedCitations":["support.sla-and-escalation@1"],"rubricPass":true},
+ {"id":"TICKET-008","scenario":"abusive message","text":"Your service is useless and your staff are idiots.","category":"abuse","slaTier":"P2","escalate":true,"allowedCitations":["support.sla-and-escalation@1"],"rubricPass":true}
+]
diff --git a/demo/fixtures/asset-registry/prompt-template.json b/demo/fixtures/asset-registry/prompt-template.json
new file mode 100644
index 00000000..21cafdc4
--- /dev/null
+++ b/demo/fixtures/asset-registry/prompt-template.json
@@ -0,0 +1,110 @@
+{
+ "objective": "Classify and draft a safe first response for one L1 support ticket",
+ "audience": "L1 support agent",
+ "useWhen": [
+ "A new synthetic or customer support ticket is ready for first triage"
+ ],
+ "doNotUseWhen": [
+ "The ticket contains a legal threat requiring counsel",
+ "The actor cannot access the approved SLA and escalation source"
+ ],
+ "textTemplate": "Using only approved support policy, classify this ticket and return the required JSON object. Ticket: {{ticket_text}}",
+ "messages": [],
+ "variables": [
+ {
+ "name": "ticket_text",
+ "type": "STRING",
+ "required": true,
+ "defaultValue": null,
+ "sensitive": true,
+ "pattern": "^TICKET-[0-9]{3}: .{3,500}$",
+ "allowedValues": []
+ }
+ ],
+ "outputContract": {
+ "type": "object",
+ "required": [
+ "category",
+ "slaTier",
+ "escalate",
+ "response"
+ ]
+ },
+ "dataPolicy": {
+ "retainRawVariables": false,
+ "retainRawOutput": false
+ },
+ "compatibility": [
+ "chat"
+ ],
+ "knowledgeRequirements": [
+ "support.sla-and-escalation@1"
+ ],
+ "evaluationCases": [
+ {
+ "name": "billing question",
+ "variables": {
+ "ticket_text": "TICKET-001: Why is my monthly invoice higher than last month?"
+ },
+ "expectedContains": ["billing", "P2", "false"],
+ "forbiddenContains": ["password", "secret"]
+ },
+ {
+ "name": "password reset",
+ "variables": {
+ "ticket_text": "TICKET-002: I cannot sign in and need a password reset."
+ },
+ "expectedContains": ["access", "P2", "false"],
+ "forbiddenContains": ["send your password", "secret"]
+ },
+ {
+ "name": "degraded service",
+ "variables": {
+ "ticket_text": "TICKET-003: Reports take ten minutes to load for every user."
+ },
+ "expectedContains": ["degraded_service", "P1", "true"],
+ "forbiddenContains": ["guarantee"]
+ },
+ {
+ "name": "confirmed outage",
+ "variables": {
+ "ticket_text": "TICKET-004: The production API is unavailable in all regions."
+ },
+ "expectedContains": ["outage", "P0", "true"],
+ "forbiddenContains": ["root cause is"]
+ },
+ {
+ "name": "suspected security issue",
+ "variables": {
+ "ticket_text": "TICKET-005: I saw an unknown login and possible account takeover."
+ },
+ "expectedContains": ["security", "P0", "true"],
+ "forbiddenContains": ["share your token"]
+ },
+ {
+ "name": "data deletion request",
+ "variables": {
+ "ticket_text": "TICKET-006: Please permanently delete my account data."
+ },
+ "expectedContains": ["privacy", "P1", "true"],
+ "forbiddenContains": ["already deleted"]
+ },
+ {
+ "name": "duplicate ticket",
+ "variables": {
+ "ticket_text": "TICKET-007: This repeats my open ticket number 4312."
+ },
+ "expectedContains": ["duplicate", "P2", "false"],
+ "forbiddenContains": ["closed"]
+ },
+ {
+ "name": "abusive message",
+ "variables": {
+ "ticket_text": "TICKET-008: Your service is useless and your staff are idiots."
+ },
+ "expectedContains": ["abuse", "P2", "true"],
+ "forbiddenContains": ["insult"]
+ }
+ ],
+ "knownLimitations": "The POC produces a first-response draft only. Refunds, deletion, incident declarations, and security remediation require accountable human approval."
+}
diff --git a/demo/fixtures/asset-registry/quality-checklist.json b/demo/fixtures/asset-registry/quality-checklist.json
new file mode 100644
index 00000000..0251fabd
--- /dev/null
+++ b/demo/fixtures/asset-registry/quality-checklist.json
@@ -0,0 +1,12 @@
+{
+ "coordinate": "support.triage-quality@1",
+ "checks": [
+ {"key": "classification", "required": true, "description": "Category matches the ticket intent"},
+ {"key": "sla", "required": true, "description": "SLA tier matches approved Knowledge"},
+ {"key": "escalation", "required": true, "description": "Escalation decision and accountable team are correct"},
+ {"key": "grounding", "required": true, "description": "SLA or escalation claims cite support.sla-and-escalation@1"},
+ {"key": "tone", "required": true, "description": "Response is calm, factual, and non-retaliatory"},
+ {"key": "schema", "required": true, "description": "Output contains category, slaTier, escalate, and response"},
+ {"key": "safety", "required": true, "description": "No secret, unsupported promise, or sensitive raw value is retained"}
+ ]
+}
diff --git a/demo/fixtures/asset-registry/success-metrics.json b/demo/fixtures/asset-registry/success-metrics.json
new file mode 100644
index 00000000..0d5205be
--- /dev/null
+++ b/demo/fixtures/asset-registry/success-metrics.json
@@ -0,0 +1,13 @@
+{
+ "definitions": [
+ {"key":"time_to_first_correct_task","formula":"correct_task_completed_at - pack_first_viewed_at","pocThreshold":"captured; no benchmark claim"},
+ {"key":"first_time_right","formula":"tasks_passing_without_reviewer_correction / attempted_tasks","pocThreshold":"1.0 for the deterministic golden ticket"},
+ {"key":"second_user_reuse","formula":"distinct_non_author_users_with_successful_use","pocThreshold":">= 1"},
+ {"key":"view_to_use","formula":"distinct_users_with_use / distinct_users_with_view","pocThreshold":"1.0 in the scripted golden flow"},
+ {"key":"evaluation_pass","formula":"passed_evaluation_cases / total_evaluation_cases","pocThreshold":"8 / 8"},
+ {"key":"reviewer_correction","formula":"revisions_with_changes_requested / submitted_revisions","pocThreshold":"captured; no benchmark claim"},
+ {"key":"owner_coverage","formula":"assets_with_active_owner_and_backup / active_assets","pocThreshold":"1.0 after handover"},
+ {"key":"unauthorized_metadata_leakage","formula":"denied_responses_containing_private_asset_metadata","pocThreshold":"0"}
+ ],
+ "measurementPolicy": "POC values are technical evidence from deterministic fixtures, not customer adoption benchmarks."
+}
diff --git a/demo/fixtures/asset-registry/support-sla-and-escalation.md b/demo/fixtures/asset-registry/support-sla-and-escalation.md
new file mode 100644
index 00000000..6a93b40f
--- /dev/null
+++ b/demo/fixtures/asset-registry/support-sla-and-escalation.md
@@ -0,0 +1,24 @@
+# L1 Support SLA And Escalation
+
+Coordinate: `support.sla-and-escalation@1`
+
+## Response tiers
+
+- P0: confirmed outage or suspected security issue. Acknowledge within 15
+ minutes and immediately escalate to incident response or security.
+- P1: degraded service or data-deletion request. Acknowledge within 1 hour and
+ escalate to the service owner or privacy team.
+- P2: billing question, password reset, duplicate ticket, or abusive message.
+ Acknowledge within 4 business hours. Escalate only when the approved Work
+ Instruction says so.
+
+## Safety rules
+
+- Never request or repeat a password, token, secret, payment-card number, or
+ unnecessary personal data.
+- Never promise a refund, deletion, restoration time, or incident cause before
+ the accountable team confirms it.
+- Cite this source as `support.sla-and-escalation@1` when an SLA or escalation
+ decision is included.
+- Use a calm, factual response for abusive messages and preserve the ticket for
+ moderator review.
diff --git a/demo/fixtures/asset-registry/work-instruction.json b/demo/fixtures/asset-registry/work-instruction.json
new file mode 100644
index 00000000..76296c4c
--- /dev/null
+++ b/demo/fixtures/asset-registry/work-instruction.json
@@ -0,0 +1,56 @@
+{
+ "purpose": "Classify and respond to one L1 support ticket",
+ "audience": "L1 support agent",
+ "prerequisites": [
+ "The ticket is assigned to the current agent",
+ "The agent can access support.sla-and-escalation@1"
+ ],
+ "completionOutcome": "The ticket has an approved category, SLA tier, escalation decision, grounded response draft, and audit trace",
+ "responsibleRole": "L1 support agent",
+ "steps": [
+ {
+ "key": "inspect",
+ "title": "Inspect the ticket",
+ "instruction": "Read only the minimum content needed and identify security, privacy, outage, and abuse signals.",
+ "expectedResult": "A bounded problem statement without copied secrets",
+ "check": "No password, token, payment-card number, or unnecessary personal data is copied",
+ "escalation": "Stop and notify security when credentials or active compromise are present",
+ "prohibitedActions": ["Request a password", "Paste a token into the Prompt"],
+ "relatedAssetIds": [],
+ "relatedKnowledgeVersionIds": []
+ },
+ {
+ "key": "ground",
+ "title": "Check SLA and escalation policy",
+ "instruction": "Use support.sla-and-escalation@1 to determine the response tier and accountable team.",
+ "expectedResult": "A P0, P1, or P2 decision with one allowed citation",
+ "check": "The selected tier and escalation match the approved Knowledge release",
+ "escalation": "Ask the support operations lead when the policy does not cover the case",
+ "prohibitedActions": ["Invent an SLA", "Promise an unconfirmed outcome"],
+ "relatedAssetIds": [],
+ "relatedKnowledgeVersionIds": []
+ },
+ {
+ "key": "draft",
+ "title": "Render and run the approved Prompt",
+ "instruction": "Use the exact support.triage-customer-ticket release pinned by the Pack.",
+ "expectedResult": "A JSON result matching the approved output contract",
+ "check": "Category, SLA tier, escalation, and response are present",
+ "escalation": "Do not send output that fails the rubric",
+ "prohibitedActions": ["Use a draft Prompt", "Silently switch to a newer release"],
+ "relatedAssetIds": [],
+ "relatedKnowledgeVersionIds": []
+ },
+ {
+ "key": "verify",
+ "title": "Verify and acknowledge",
+ "instruction": "Apply support.triage-quality@1, correct any failure, record the final decision, and acknowledge this instruction.",
+ "expectedResult": "The ticket passes the rubric and the Pack item is complete",
+ "check": "All required checklist items pass",
+ "escalation": "Route failed or ambiguous cases to the support operations lead",
+ "prohibitedActions": ["Hide an evaluation failure", "Mark an inaccessible item complete"],
+ "relatedAssetIds": [],
+ "relatedKnowledgeVersionIds": []
+ }
+ ]
+}
diff --git a/docs/increments/active/README.md b/docs/increments/active/README.md
index b1e7a5c5..4c2dc85a 100644
--- a/docs/increments/active/README.md
+++ b/docs/increments/active/README.md
@@ -15,9 +15,3 @@ progress here. Consolidate current behavior before moving an increment to
permission evaluation dataset.
3. Prove the Slack connector against a real workspace, including member removal
and the next-crawl access revocation.
-4. Validate and execute the
- [prompt-first unified Asset Registry program](2026-07-25-unified-asset-registry-definition/plan.md):
- pass the architecture/design-partner gate, then land the registry kernel,
- authorization, Prompt Template, Work Instruction, Capability Pack,
- federated Knowledge, Assistant, generic web, and authenticated read-only MCP
- PRs before proving the L1 Support role-onboarding outcome.
diff --git a/docs/increments/active/2026-07-25-unified-asset-registry-definition/design.md b/docs/increments/completed/2026-07-25-unified-asset-registry-definition/design.md
similarity index 98%
rename from docs/increments/active/2026-07-25-unified-asset-registry-definition/design.md
rename to docs/increments/completed/2026-07-25-unified-asset-registry-definition/design.md
index 22ca526c..d7b350d3 100644
--- a/docs/increments/active/2026-07-25-unified-asset-registry-definition/design.md
+++ b/docs/increments/completed/2026-07-25-unified-asset-registry-definition/design.md
@@ -545,9 +545,11 @@ Rejected alternatives:
- expose every application action as an MCP tool;
- implement a generic BPM or Agent runtime.
-The named independent debate did not occur. Stakeholder validation with a
-support/operations process owner and an AI power user also remains open and is
-required before PR 5 can claim the POC is complete.
+The named independent debate did not occur. The repository POC closes through
+the product-owner accepted deterministic integration and two-session browser
+proof in PR 5. No external support-operations stakeholder or customer adoption
+validation is claimed; that evidence belongs to a pilot follow-on rather than
+being implied by automated acceptance.
## POC Success Gate
diff --git a/docs/increments/active/2026-07-25-unified-asset-registry-definition/gate-decisions.md b/docs/increments/completed/2026-07-25-unified-asset-registry-definition/gate-decisions.md
similarity index 88%
rename from docs/increments/active/2026-07-25-unified-asset-registry-definition/gate-decisions.md
rename to docs/increments/completed/2026-07-25-unified-asset-registry-definition/gate-decisions.md
index 0a707992..bb53dbe3 100644
--- a/docs/increments/active/2026-07-25-unified-asset-registry-definition/gate-decisions.md
+++ b/docs/increments/completed/2026-07-25-unified-asset-registry-definition/gate-decisions.md
@@ -17,11 +17,11 @@ The fixture is synthetic and contains no customer or employee data.
| Capability Pack | `support.l1-onboarding@1.0.0` | Ordered required Knowledge, Work Instruction, and Prompt pins |
| Evaluation rubric | `support.triage-quality@1` | Classification, SLA, escalation, grounding, tone, and schema checks |
-PR 5 will materialize eight deterministic mock tickets: billing question,
-password reset, degraded service, confirmed outage, suspected security issue,
+PR 5 materializes eight deterministic mock tickets: billing question, password
+reset, degraded service, confirmed outage, suspected security issue,
data-deletion request, duplicate ticket, and abusive message. Expected labels,
SLA tier, escalation decision, allowed citations, and rubric result are pinned
-in the fixture. Until then this table is the frozen semantic contract.
+under `demo/fixtures/asset-registry`.
Two actors prove the flow:
@@ -110,3 +110,13 @@ token exchange/on-behalf-of; the gateway must not reinterpret the API token as
an MCP token. Object authorization remains OpenFGA-backed and cannot be
replaced by OAuth scopes. PR 4 is read-only: no prompt execution, progress mutation,
review, publication, withdrawal, permission change, or installation.
+
+## POC Closure Decision
+
+Technical closure requires the deterministic integration flow, separate-owner
+and second-user browser sessions, opaque denial coverage, full repository
+gates, OpenFGA model verification, generated-contract parity, and a terminating
+context load. Metrics produced by this fixture are technical acceptance
+evidence, not customer adoption benchmarks. Screenpipe, public marketplace,
+Skill installation, controlled SOP, executable Workflow/Agent/Tool profiles,
+and public MCP mutation remain separate follow-on increments.
diff --git a/docs/increments/active/2026-07-25-unified-asset-registry-definition/plan.md b/docs/increments/completed/2026-07-25-unified-asset-registry-definition/plan.md
similarity index 89%
rename from docs/increments/active/2026-07-25-unified-asset-registry-definition/plan.md
rename to docs/increments/completed/2026-07-25-unified-asset-registry-definition/plan.md
index bf2d802b..58300971 100644
--- a/docs/increments/active/2026-07-25-unified-asset-registry-definition/plan.md
+++ b/docs/increments/completed/2026-07-25-unified-asset-registry-definition/plan.md
@@ -40,9 +40,10 @@ invariants, and their integration tests merely to reduce the file count.
- [x] Record the product-owner waiver of the independent Claude Fable 5 debate
on 2026-07-25. This is an explicit exception, not a claim that the review ran.
-- [ ] Validate the L1 Support onboarding story with one support/operations
- process owner and one AI power user. Engineering implementation is authorized
- to proceed, but PR 5 cannot claim POC completion until this validation occurs.
+- [x] Validate the L1 Support onboarding story through the product-owner
+ accepted deterministic technical POC: distinct author/reviewer/second-user
+ integration actors plus separate owner/support-agent browser sessions.
+ External field validation is not claimed and belongs to the pilot follow-on.
- [x] Freeze a small demo-safe fixture: authorized Knowledge, one Work
Instruction, one Prompt Template, one Pack, five to ten mock tickets, and one
evaluation rubric in `gate-decisions.md`.
@@ -50,9 +51,10 @@ invariants, and their integration tests merely to reduce the file count.
matrix, retention defaults, and OAuth protected-resource/audience decision in
`gate-decisions.md`.
-The product owner explicitly authorized PR 1 to start with the named-review
-waiver and stakeholder validation still open. That validation remains a hard
-completion gate for PR 5.
+The product owner explicitly authorized PR 1 with the named-review waiver and
+later directed the five-PR sequence through technical completion. PR 5 closes
+the repository POC with deterministic acceptance evidence; customer adoption
+or external support-operations validation remains a separate pilot outcome.
## PR Dependency Graph
@@ -259,7 +261,7 @@ Required gates:
- [x] Tool descriptions do not grant authority.
- [x] Retrieved Asset content cannot override system/policy instructions.
-- [ ] Two-user tests cover recommendation, Pack, Prompt, Knowledge, and
+- [x] Two-user tests cover recommendation, Pack, Prompt, Knowledge, and
citations.
- [x] Every applicable trace contains exact releases without raw secrets.
- [x] Assistant has no hidden governance tool path.
@@ -267,7 +269,8 @@ Required gates:
- [x] Generic detail renders every POC profile without Prompt-specific routing.
- [x] Role/permission switch partitions permission-filtered Asset caches by
organization, actor, department, and session role.
-- [ ] Real-browser author -> review -> release -> second-user Pack journey.
+- [x] Real-browser author -> review -> release -> second-user Pack journey
+ (closed by the PR 5 golden POC).
Explicitly excluded:
@@ -338,31 +341,33 @@ Expected size: 20-50 files.
Scope:
-- [ ] Add demo-safe L1 Support Knowledge, Work Instruction, Prompt, checklist,
+- [x] Add demo-safe L1 Support Knowledge, Work Instruction, Prompt, checklist,
mock tickets, rubric, and Pack fixtures.
-- [ ] Prove author -> review -> release -> second-user discovery -> Prompt run
+- [x] Prove author -> review -> release -> second-user discovery -> Prompt run
-> Work Instruction/Pack completion.
-- [ ] Prove one Prompt replacement without silent Pack mutation.
-- [ ] Prove withdrawal blocks new use and remains auditable.
-- [ ] Prove owner/backup-owner handover and flag orphaned Assets.
-- [ ] Capture time-to-first-correct-task, first-time-right, second-user reuse,
+- [x] Prove one Prompt replacement without silent Pack mutation.
+- [x] Prove withdrawal blocks new use and remains auditable.
+- [x] Prove owner/backup-owner handover and flag orphaned Assets.
+- [x] Capture time-to-first-correct-task, first-time-right, second-user reuse,
view-to-use, evaluation pass, reviewer correction, and owner coverage.
-- [ ] Run static, domain, integration, OpenFGA, frontend, browser, MCP, and
+- [x] Run static, domain, integration, OpenFGA, frontend, browser, MCP, and
two-user security gates.
-- [ ] Consolidate implemented facts into architecture/spec/test/decision docs.
-- [ ] Move this increment to `completed` only when every required POC gate has
+- [x] Consolidate implemented facts into architecture/spec/test/decision docs.
+- [x] Move this increment to `completed` only when every required POC gate has
evidence.
Required gates:
-- [ ] `.\gradlew.bat --no-daemon clean test`
-- [ ] OpenFGA model validate and model test
-- [ ] generated OpenAPI drift check
-- [ ] `corepack pnpm -C web typecheck`
-- [ ] `corepack pnpm -C web build`
-- [ ] real-browser two-user POC
-- [ ] generic denied-resource behavior across REST, Assistant, and MCP
-- [ ] terminating context-load test
+- [x] `.\gradlew.bat --no-daemon clean test`
+- [x] OpenFGA model validate and model test
+- [x] generated OpenAPI drift check
+- [x] `corepack pnpm -C web typecheck`
+- [x] `corepack pnpm -C web build`
+- [x] real-browser two-user POC
+- [x] generic denied-resource behavior across REST, Assistant, and MCP
+- [x] terminating context-load test
+
+Verification evidence is consolidated in [verification.md](verification.md).
## Follow-On Increments, Not Hidden PRs
diff --git a/docs/increments/active/2026-07-25-unified-asset-registry-definition/ui-reference-audit.md b/docs/increments/completed/2026-07-25-unified-asset-registry-definition/ui-reference-audit.md
similarity index 100%
rename from docs/increments/active/2026-07-25-unified-asset-registry-definition/ui-reference-audit.md
rename to docs/increments/completed/2026-07-25-unified-asset-registry-definition/ui-reference-audit.md
diff --git a/docs/increments/completed/2026-07-25-unified-asset-registry-definition/verification.md b/docs/increments/completed/2026-07-25-unified-asset-registry-definition/verification.md
new file mode 100644
index 00000000..bca541d1
--- /dev/null
+++ b/docs/increments/completed/2026-07-25-unified-asset-registry-definition/verification.md
@@ -0,0 +1,79 @@
+# Asset Registry POC Verification
+
+Date: 2026-07-26
+
+## Golden business flow
+
+`AssetRegistryIntegrationTests#goldenPocTransfersAReleasedSupportCapabilityToASecondUser`
+uses the synthetic files under `demo/fixtures/asset-registry` and proves:
+
+1. an operations lead authors Prompt, Work Instruction, and Capability Pack
+ drafts;
+2. an independent reviewer approves each immutable digest;
+3. exact releases are published and a distinct support agent discovers the
+ role Pack without receiving an Asset ID;
+4. all eight bounded Prompt cases pass with permission-aware Knowledge
+ grounding;
+5. the second user completes one grounded Prompt run, acknowledges the Work
+ Instruction, and completes the exact-pin Pack;
+6. Prompt `2.0.0` does not rewrite the Pack's `1.0.0` pin;
+7. withdrawal blocks new use of the old Prompt and leaves an append-only audit
+ event;
+8. active owner/backup assignments produce complete ownership health, while a
+ missing assignment produces a visible continuity-risk flag.
+
+`asset-registry-golden-poc.spec.ts` repeats the user-facing proof in separate
+real Chromium sessions: the owner sees approved review/release history; the
+support agent sees only the permitted Pack, follows the exact release, sees
+owner and backup coverage, and reaches 100% progress.
+
+## Deterministic POC metrics
+
+These values are technical acceptance evidence, not customer benchmarks.
+
+| Metric | POC evidence |
+| --- | --- |
+| Time to first correct task | start/completion timestamps and Prompt duration are captured; no adoption benchmark claimed |
+| First-time-right | 1/1 scripted golden task |
+| Second-user reuse | one distinct non-author support agent |
+| View-to-use | 1/1 in the scripted browser flow |
+| Evaluation pass | 8/8 bounded ticket cases |
+| Reviewer correction | 0/4 golden submissions required a correction; the workflow still supports request-changes |
+| Owner coverage | 3/3 active golden registry Assets have an owner and backup after handover |
+| Unauthorized metadata leakage | zero; REST, Assistant, Pack, and MCP denial tests remain opaque |
+
+## Gate evidence
+
+- `.\gradlew.bat --no-daemon clean test` — passed, 97 actionable tasks.
+- OpenFGA CLI `v0.7.19`:
+ - `fga model validate` — `is_valid: true`;
+ - `fga model test` — 8/8 tests, 66/66 checks, 27/27 ListObjects.
+- `corepack pnpm -C web check:api` — generated client matches committed
+ OpenAPI.
+- `corepack pnpm -C web typecheck` — passed.
+- `corepack pnpm -C web build` — passed; only the existing chunk-size warning
+ remains.
+- `corepack pnpm -C web test:e2e` — 7/7 Chromium tests passed.
+- `OrgMemoryApiContextLoadTests` and `OrgMemoryMcpContextTests` — passed and
+ terminated cleanly.
+- Generic denial evidence:
+ - REST/cross-tenant:
+ `AssetRegistryIntegrationTests#unauthorizedAndCrossTenantIdsAreOpaqueWhileListIntersectsCanonicalRows`;
+ - Pack/Assistant:
+ `CapabilityPackServiceTests` and `AssistantAssetToolServiceTests`;
+ - MCP:
+ `AssetDeliveryApiClientTests`, `McpTokenValidationTests`, and
+ `AssetDeliveryControllerSecurityTests`.
+
+JetBrains inspection cannot target this feature worktree while the IDE project
+is `D:\OrgMemory`. Full backend compile/clean tests plus web lint/typecheck,
+generated-contract drift, diff hygiene, and browser gates are the static
+fallback.
+
+## Scope statement
+
+The repository POC is technically complete. It does not claim customer
+adoption or external support-operations stakeholder validation. Screenpipe,
+public marketplace, controlled SOP, Skill installation, public MCP mutation,
+and executable Workflow/Agent/Tool profiles remain separate follow-on
+increments.
diff --git a/docs/increments/completed/README.md b/docs/increments/completed/README.md
index 57a452dd..eaaa2386 100644
--- a/docs/increments/completed/README.md
+++ b/docs/increments/completed/README.md
@@ -2,3 +2,8 @@
Completed increments are historical evidence. Current behavior belongs in
`ARCHITECTURE.md`, specs, tests, and accepted decisions.
+
+- [Prompt-first unified Asset Registry POC](2026-07-25-unified-asset-registry-definition/plan.md):
+ generic governed registry, Prompt/Work Instruction/Pack profiles, federated
+ Knowledge, Assistant and four web surfaces, authenticated read-only MCP, and
+ the deterministic two-user L1 Support golden proof.
diff --git a/docs/roadmap.md b/docs/roadmap.md
index f607951f..d4f16bc4 100644
--- a/docs/roadmap.md
+++ b/docs/roadmap.md
@@ -53,6 +53,12 @@ belongs in one active increment.
citations, the permission-aware graph explorer, evaluation harness, and
OpenTelemetry-compatible events. Final integration PR #42 is on `main`;
remaining live quality/performance evidence belongs to pilot hardening.
+- The five-PR
+ [prompt-first unified Asset Registry POC](increments/completed/2026-07-25-unified-asset-registry-definition/plan.md):
+ governed generic identity/revision/review/release lifecycle; Prompt Template,
+ Work Instruction, exact-pin Capability Pack, federated Knowledge; in-app
+ Assistant and four generic web surfaces; authenticated read-only MCP; and a
+ deterministic two-user L1 Support golden flow with 8/8 bounded evaluations.
## Active Delivery
@@ -87,17 +93,6 @@ belongs in one active increment.
5. Give a Knowledge Space a lifecycle. It can be created and granted at runtime
but not retired, and asset movement still needs an explicit retention and
authorization contract.
-6. Execute the
- [prompt-first unified Asset Registry program](increments/active/2026-07-25-unified-asset-registry-definition/plan.md):
- pass the independent architecture debate and design-partner gate, then land
- the registry kernel, Asset authorization, Prompt Template, Work Instruction,
- Capability Pack, federated Knowledge, Assistant, generic web, and authenticated
- read-only MCP PRs in dependency order.
-7. Prove the first typed Asset outcome: an L1 Support onboarding Pack lets a
- second authorized user complete one realistic task with exact released
- Knowledge, Work Instruction, and Prompt components; then prove update,
- withdrawal, owner handover, audit, and denied-component opacity.
-
## Pilot Hardening
- S3-compatible production blobs, malware/DLP integration, retention/deletion.
diff --git a/docs/specs/domains/asset-registry.md b/docs/specs/domains/asset-registry.md
index 13cfcedf..67420a3f 100644
--- a/docs/specs/domains/asset-registry.md
+++ b/docs/specs/domains/asset-registry.md
@@ -12,6 +12,11 @@ Consumers always address an exact authorized release. A withdrawn release
cannot start new consumption. Forking creates a new Asset draft from an exact
release payload and does not copy reviews or approvals.
+Every Asset view derives ownership health from active role assignments:
+`ownerPresent`, `backupOwnerPresent`, `orphaned`, and `continuityAtRisk`.
+Missing ownership coverage is visible in the shared release header; it never
+changes release bytes or grants authorization.
+
### Prompt Template
A Prompt Template release contains exactly one text template or ordered
@@ -114,6 +119,15 @@ MCP audience, and rate-limits each authenticated subject. The POC forwards a
dual-audience bearer that also contains the API audience. Deployments that
cannot issue that token require explicit token exchange/on-behalf-of.
+### Golden POC Fixture
+
+`demo/fixtures/asset-registry` is the synthetic, deterministic L1 Support
+fixture. It fixes one Knowledge source, one Work Instruction, one Prompt
+Template with eight bounded ticket cases, one exact-pin onboarding Pack, one
+quality checklist, and metric definitions. The integration proof uses a
+distinct author, reviewer, and second user; the real-browser proof uses
+separate owner and support-agent sessions.
+
## Source Modules
- `core.assetregistry`
diff --git a/docs/tests/domains/asset-registry.md b/docs/tests/domains/asset-registry.md
index 057e7778..70ee3235 100644
--- a/docs/tests/domains/asset-registry.md
+++ b/docs/tests/domains/asset-registry.md
@@ -29,3 +29,8 @@
| MCP rejects wrong audience, issuer, and expired tokens | `McpTokenValidationTests` | covered |
| MCP advertises RFC 9728 metadata and challenges unauthenticated requests with its location | `OrgMemoryMcpContextTests` | covered |
| MCP applies a per-subject request limit without logging or returning tokens | `McpRateLimitFilterTests` | covered |
+| Golden L1 Support fixture passes eight bounded Prompt cases with permission-aware grounding | `AssetRegistryIntegrationTests#goldenPocTransfersAReleasedSupportCapabilityToASecondUser`, `demo/fixtures/asset-registry` | covered |
+| Second-user discovery, exact-release Prompt use, Work Instruction acknowledgement, Pack completion, replacement pin stability, and withdrawal are one integrated flow | `AssetRegistryIntegrationTests#goldenPocTransfersAReleasedSupportCapabilityToASecondUser` | covered |
+| Active owner/backup coverage derives an explicit orphaned and continuity-risk flag | `AssetRegistryIntegrationTests#goldenPocTransfersAReleasedSupportCapabilityToASecondUser`, `AssetIdentityHeader` | covered |
+| Owner governance and second-user Pack completion render in separate real browser sessions | `asset-registry-golden-poc.spec.ts` | covered |
+| Generic denial stays opaque across REST, Assistant, and MCP | `AssetRegistryIntegrationTests#unauthorizedAndCrossTenantIdsAreOpaqueWhileListIntersectsCanonicalRows`, `AssetDeliveryApiClientTests`, `CapabilityPackServiceTests` | covered |
diff --git a/docs/vision.md b/docs/vision.md
index fc0ba9a9..6fe3de5e 100644
--- a/docs/vision.md
+++ b/docs/vision.md
@@ -187,16 +187,15 @@ bypass authorization.
## Web Direction
-The current registry UI is disposable prototype evidence. The replacement is an
-agent-first workspace centered on one Asset Registry with four generic
-surfaces: **For you / Asset catalog**, **Asset detail / use**, **Pack journey**,
-and **Governance workspace**. Asset type profiles supply their renderer and
-actions; the product must not hard-code a Prompt-only page hierarchy. Search,
-ask, citations, release history, provenance, permissions, source health, and
-later Skill installation reuse that shell. A Skill Registry is a filtered
-installable view of the shared catalog, not another lifecycle. Reuse shadcn/ui
-and maintained libraries, keep light and dark themes, and avoid porting old
-page composition merely for parity.
+The POC implements an agent-first workspace centered on one Asset Registry with
+four generic surfaces: **For you / Asset catalog**, **Asset detail / use**,
+**Pack journey**, and **Governance workspace**. Asset type profiles supply
+their renderer and actions; the product does not hard-code a Prompt-only page
+hierarchy. Search, ask, citations, release history, provenance, permissions,
+source health, and later Skill installation reuse that shell. A Skill Registry
+is a filtered installable view of the shared catalog, not another lifecycle.
+Production UX may evolve from this evidence, while retaining the same
+permission and exact-release contracts.
## Non-Goals For The First Pilot
diff --git a/web/src/features/assets/components/asset-detail-page.tsx b/web/src/features/assets/components/asset-detail-page.tsx
index 0da2b092..f5801dd5 100644
--- a/web/src/features/assets/components/asset-detail-page.tsx
+++ b/web/src/features/assets/components/asset-detail-page.tsx
@@ -191,6 +191,15 @@ function AssetIdentityHeader({
{meta.label}
{asset.portfolioState}
+ {asset.ownershipHealth?.orphaned ? (
+
+ Orphaned
+
+ ) : asset.ownershipHealth?.continuityAtRisk ? (
+
+ Ownership gap
+
+ ) : null}
{release?.availability === "DEPRECATED" ? (
Deprecated
diff --git a/web/test/e2e/asset-registry-golden-poc.spec.ts b/web/test/e2e/asset-registry-golden-poc.spec.ts
new file mode 100644
index 00000000..33551df2
--- /dev/null
+++ b/web/test/e2e/asset-registry-golden-poc.spec.ts
@@ -0,0 +1,345 @@
+import { expect, test, type Page, type Route } from "@playwright/test"
+
+const PACK_ID = "a1000000-0000-0000-0000-000000000001"
+const PACK_RELEASE_ID = "a1000000-0000-0000-0000-000000000002"
+const PACK_REVISION_ID = "a1000000-0000-0000-0000-000000000003"
+const REVIEW_ID = "a1000000-0000-0000-0000-000000000004"
+const OWNER_ID = "44444444-4444-4444-4444-444444444444"
+const REVIEWER_ID = "55555555-5555-5555-5555-555555555555"
+const SUPPORT_AGENT_ID = "66666666-6666-6666-6666-666666666666"
+const BACKUP_OWNER_ID = "77777777-7777-7777-7777-777777777777"
+const ORGANIZATION_ID = "11111111-1111-1111-1111-111111111111"
+const DEPARTMENT_ID = "33333333-3333-3333-3333-333333333333"
+const INSTRUCTION_ID = "a2000000-0000-0000-0000-000000000001"
+const INSTRUCTION_RELEASE_ID = "a2000000-0000-0000-0000-000000000002"
+const PROMPT_ID = "a3000000-0000-0000-0000-000000000001"
+const PROMPT_RELEASE_ID = "a3000000-0000-0000-0000-000000000002"
+
+test("two users prove governed release and second-user Pack completion", async ({
+ browser,
+ baseURL,
+}) => {
+ const ownerContext = await browser.newContext({ baseURL })
+ const ownerPage = await ownerContext.newPage()
+ const ownerHarness = await assetHarness(ownerPage, "owner")
+
+ await ownerPage.goto(`/assets/${PACK_ID}/governance`)
+ await expect(ownerPage.getByRole("heading", { name: "Governance workspace" })).toBeVisible()
+ await ownerPage.getByRole("tab", { name: "Review" }).click()
+ await expect(ownerPage.getByText("APPROVED", { exact: true })).toBeVisible()
+ await expect(ownerPage.getByText("Approved by independent reviewer")).toBeVisible()
+ await ownerPage.getByRole("tab", { name: "Releases" }).click()
+ await expect(ownerPage.getByText("1.0.0", { exact: true })).toBeVisible()
+ await expect(ownerPage.getByText("AVAILABLE", { exact: true })).toBeVisible()
+ expect(ownerHarness.unexpectedRequests).toEqual([])
+ expect(ownerHarness.browserErrors).toEqual([])
+ await ownerContext.close()
+
+ const supportContext = await browser.newContext({ baseURL })
+ const supportPage = await supportContext.newPage()
+ const supportHarness = await assetHarness(supportPage, "support")
+
+ await supportPage.goto("/assets")
+ await expect(supportPage.getByRole("heading", { name: "For your role" })).toBeVisible()
+ await expect(
+ supportPage.getByRole("heading", { name: "L1 Customer Support Capability Onboarding" }),
+ ).toBeVisible()
+ await expect(supportPage.getByText("Restricted Security Prompt")).toHaveCount(0)
+ await supportPage.getByRole("link", { name: "Use exact release" }).click()
+ await expect(supportPage.getByText(`Owner: ${SUPPORT_AGENT_ID}`)).toBeVisible()
+ await expect(supportPage.getByText(`Backup: ${BACKUP_OWNER_ID}`)).toBeVisible()
+ await expect(supportPage.getByText("Ownership gap")).toHaveCount(0)
+ await supportPage.getByRole("link", { name: "Start or resume journey" }).click()
+
+ await expect(supportPage.getByRole("heading", { name: "L1 Customer Support Capability Onboarding" })).toBeVisible()
+ await expect(supportPage.getByText("0%")).toBeVisible()
+ await supportPage.getByRole("button", { name: "Mark complete: Classify and respond" }).click()
+ await expect(supportPage.getByText("50%")).toBeVisible()
+ await supportPage.getByRole("button", { name: "Mark complete: Triage customer ticket" }).click()
+ await expect(supportPage.getByText("100%")).toBeVisible()
+ await expect(supportPage.getByText("COMPLETED", { exact: true })).toBeVisible()
+
+ expect(supportHarness.unexpectedRequests).toEqual([])
+ expect(supportHarness.browserErrors).toEqual([])
+ expect(supportHarness.requests).toContain("GET /api/assistant/tools/asset-recommendations")
+ expect(
+ supportHarness.requests.filter((request) => request.startsWith("PUT /api/assets/")),
+ ).toHaveLength(2)
+ await supportContext.close()
+})
+
+async function assetHarness(page: Page, actor: "owner" | "support") {
+ const requests: string[] = []
+ const unexpectedRequests: string[] = []
+ const browserErrors: string[] = []
+ const completed = new Set()
+
+ page.on("pageerror", (error) => browserErrors.push(error.message))
+ page.on("console", (message) => {
+ if (message.type() === "error") browserErrors.push(message.text())
+ })
+
+ await page.route("**/api/**", async (route) => {
+ const request = route.request()
+ const url = new URL(request.url())
+ const signature = `${request.method()} ${url.pathname}`
+ requests.push(signature)
+
+ if (url.pathname === "/api/session") {
+ await json(route, session(actor))
+ return
+ }
+ if (url.pathname === "/api/session/csrf") {
+ await route.fulfill({
+ status: 200,
+ contentType: "application/json",
+ headers: { "set-cookie": "XSRF-TOKEN=golden-poc-token; Path=/" },
+ body: JSON.stringify({
+ headerName: "X-XSRF-TOKEN",
+ parameterName: "_csrf",
+ token: "golden-poc-token",
+ }),
+ })
+ return
+ }
+ if (
+ actor === "support" &&
+ url.pathname === "/api/assistant/tools/asset-recommendations"
+ ) {
+ await json(route, {
+ traceId: "a4000000-0000-0000-0000-000000000001",
+ recommendations: [
+ {
+ assetId: PACK_ID,
+ type: "CAPABILITY_PACK",
+ namespace: "support",
+ slug: "l1-onboarding",
+ title: "L1 Customer Support Capability Onboarding",
+ summary: "Complete the first correct L1 support ticket",
+ knowledgeSpaceId: "88888888-8888-4888-8888-888888888802",
+ portfolioState: "ACTIVE",
+ releaseId: PACK_RELEASE_ID,
+ versionLabel: "1.0.0",
+ releaseDigest: "a".repeat(64),
+ availability: "AVAILABLE",
+ },
+ ],
+ })
+ return
+ }
+ if (url.pathname === `/api/assets/${PACK_ID}`) {
+ await json(route, packAsset())
+ return
+ }
+ if (
+ actor === "support" &&
+ url.pathname ===
+ `/api/assets/${PACK_ID}/releases/${PACK_RELEASE_ID}/pack-journey`
+ ) {
+ await json(route, packJourney(completed))
+ return
+ }
+ if (
+ actor === "support" &&
+ request.method() === "PUT" &&
+ url.pathname.startsWith(
+ `/api/assets/${PACK_ID}/releases/${PACK_RELEASE_ID}/pack-progress/`,
+ )
+ ) {
+ completed.add(url.pathname.split("/").at(-1)!)
+ await json(route, packJourney(completed))
+ return
+ }
+
+ unexpectedRequests.push(signature)
+ await json(route, { message: "Unexpected golden POC request" }, 500)
+ })
+
+ return { requests, unexpectedRequests, browserErrors }
+}
+
+function session(actor: "owner" | "support") {
+ return {
+ authenticated: true,
+ name: actor === "owner" ? "Operations Lead" : "Support Agent",
+ email: actor === "owner" ? "lead@example.test" : "agent@example.test",
+ userId: actor === "owner" ? OWNER_ID : SUPPORT_AGENT_ID,
+ organizationId: ORGANIZATION_ID,
+ departmentId: DEPARTMENT_ID,
+ role: actor === "owner" ? "MANAGER" : "EMPLOYEE",
+ }
+}
+
+function packAsset() {
+ const payload = JSON.stringify({
+ purpose: "ROLE_ONBOARDING",
+ audience: "L1 support agent",
+ prerequisites: ["Active support account"],
+ expectedOutcome: "Complete the first correct L1 support ticket",
+ items: [
+ { key: "instruction", required: true, kind: "REGISTRY_RELEASE" },
+ { key: "prompt", required: true, kind: "REGISTRY_RELEASE" },
+ ],
+ completionCriteria: ["Required items complete"],
+ })
+ const timestamp = "2026-07-26T00:00:00Z"
+ return {
+ id: PACK_ID,
+ type: "CAPABILITY_PACK",
+ namespace: "support",
+ slug: "l1-onboarding",
+ knowledgeSpaceId: "88888888-8888-4888-8888-888888888802",
+ portfolioState: "ACTIVE",
+ authorizationReady: true,
+ draft: {
+ id: "a1000000-0000-0000-0000-000000000005",
+ lockVersion: 1,
+ title: "L1 Customer Support Capability Onboarding",
+ summary: "Complete the first correct L1 support ticket",
+ classification: "INTERNAL",
+ schemaVersion: "1",
+ payload,
+ editedByUserId: OWNER_ID,
+ updatedAt: timestamp,
+ },
+ revisions: [
+ {
+ id: PACK_REVISION_ID,
+ sequence: 1,
+ title: "L1 Customer Support Capability Onboarding",
+ summary: "Complete the first correct L1 support ticket",
+ classification: "INTERNAL",
+ schemaVersion: "1",
+ payload,
+ digest: "a".repeat(64),
+ changeNote: "Initial L1 onboarding release",
+ createdByUserId: OWNER_ID,
+ createdAt: timestamp,
+ },
+ ],
+ reviews: [
+ {
+ id: REVIEW_ID,
+ revisionId: PACK_REVISION_ID,
+ revisionDigest: "a".repeat(64),
+ state: "APPROVED",
+ policyVersion: "asset-review-v1",
+ requestedByUserId: OWNER_ID,
+ createdAt: timestamp,
+ resolvedAt: timestamp,
+ decisions: [
+ {
+ reviewerUserId: REVIEWER_ID,
+ decision: "APPROVE",
+ comment: "Approved by independent reviewer",
+ decidedAt: timestamp,
+ },
+ ],
+ },
+ ],
+ releases: [
+ {
+ id: PACK_RELEASE_ID,
+ revisionId: PACK_REVISION_ID,
+ sequence: 1,
+ versionLabel: "1.0.0",
+ title: "L1 Customer Support Capability Onboarding",
+ summary: "Complete the first correct L1 support ticket",
+ classification: "INTERNAL",
+ schemaVersion: "1",
+ payload,
+ digest: "a".repeat(64),
+ releasedByUserId: OWNER_ID,
+ releasedAt: timestamp,
+ availability: "AVAILABLE",
+ availabilityHistory: [
+ {
+ availability: "AVAILABLE",
+ reason: "Initial release",
+ changedByUserId: OWNER_ID,
+ effectiveAt: timestamp,
+ },
+ ],
+ },
+ ],
+ ownershipHealth: {
+ ownerPresent: true,
+ backupOwnerPresent: true,
+ orphaned: false,
+ continuityAtRisk: false,
+ },
+ roleAssignments: [
+ roleAssignment(SUPPORT_AGENT_ID, "OWNER"),
+ roleAssignment(BACKUP_OWNER_ID, "BACKUP_OWNER"),
+ ],
+ }
+}
+
+function roleAssignment(principalId: string, role: "OWNER" | "BACKUP_OWNER") {
+ return {
+ id: role === "OWNER"
+ ? "a5000000-0000-0000-0000-000000000001"
+ : "a5000000-0000-0000-0000-000000000002",
+ principalType: "user",
+ principalId,
+ role,
+ validFrom: "2026-07-26T00:00:00Z",
+ assignedByUserId: OWNER_ID,
+ projectedAt: "2026-07-26T00:00:01Z",
+ }
+}
+
+function packJourney(completed: Set) {
+ const items = [
+ {
+ key: "instruction",
+ required: true,
+ order: 1,
+ kind: "REGISTRY_RELEASE",
+ resourceId: INSTRUCTION_ID,
+ pinnedVersionId: INSTRUCTION_RELEASE_ID,
+ title: "Classify and respond",
+ versionLabel: "1.0.0",
+ availability: "AVAILABLE",
+ completed: completed.has("instruction"),
+ },
+ {
+ key: "prompt",
+ required: true,
+ order: 2,
+ kind: "REGISTRY_RELEASE",
+ resourceId: PROMPT_ID,
+ pinnedVersionId: PROMPT_RELEASE_ID,
+ title: "Triage customer ticket",
+ versionLabel: "1.0.0",
+ availability: "AVAILABLE",
+ completed: completed.has("prompt"),
+ },
+ ]
+ return {
+ assignmentId: "a6000000-0000-0000-0000-000000000001",
+ packAssetId: PACK_ID,
+ packReleaseId: PACK_RELEASE_ID,
+ releaseDigest: "a".repeat(64),
+ title: "L1 Customer Support Capability Onboarding",
+ versionLabel: "1.0.0",
+ purpose: "ROLE_ONBOARDING",
+ audience: "L1 support agent",
+ expectedOutcome: "Complete the first correct L1 support ticket",
+ status: completed.size === items.length ? "COMPLETED" : "IN_PROGRESS",
+ accessGap: false,
+ completedAccessibleItems: completed.size,
+ items,
+ startedAt: "2026-07-26T00:00:00Z",
+ completedAt:
+ completed.size === items.length ? "2026-07-26T00:05:00Z" : undefined,
+ }
+}
+
+async function json(route: Route, body: unknown, status = 200) {
+ await route.fulfill({
+ status,
+ contentType: "application/json",
+ body: JSON.stringify(body),
+ })
+}