create-egregore 0.21.6 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/egregore-launcher.js +58 -6
- package/assets/egregore-launcher.sh +15 -2
- package/lib/claude-runtime.js +2 -0
- package/lib/codex-runtime.js +2 -0
- package/lib/setup.js +20 -3
- package/package.json +2 -2
- package/runtime/claude/.claude/context/commit-format.md +7 -4
- package/runtime/claude/.claude/context/handoff-claiming.md +32 -0
- package/runtime/claude/.claude/context/notification-consent.md +12 -9
- package/runtime/claude/.claude/hooks/boundary-check.sh +14 -331
- package/runtime/claude/.claude/hooks/memory-read-guard.sh +9 -1
- package/runtime/claude/.claude/rules/voice-bedrock.md +6 -2
- package/runtime/claude/.claude/settings.json +1 -1
- package/runtime/claude/.claude/skills/commit/SKILL.md +5 -3
- package/runtime/claude/.claude/skills/dark-mode/SKILL.md +48 -369
- package/runtime/claude/.claude/skills/records/SKILL.md +158 -0
- package/runtime/claude/.claude/skills/reflect/SKILL.md +50 -21
- package/runtime/claude/.claude/skills/review-pr/SKILL.md +4 -2
- package/runtime/claude/.claude/skills/save/SKILL.md +9 -3
- package/runtime/claude/.claude/skills/summon/SKILL.md +138 -207
- package/runtime/claude/.claude/skills/tui-design/SKILL.md +1 -1
- package/runtime/claude/.claude/skills/update/SKILL.md +1 -1
- package/runtime/claude/.claude/skills/view/SKILL.md +52 -16
- package/runtime/claude/.claude/skills/view/compose-scaffold.html +19 -13
- package/runtime/claude/CLAUDE.md +59 -95
- package/runtime/claude/bin/agent.sh +231 -7
- package/runtime/claude/bin/api-call.sh +44 -23
- package/runtime/claude/bin/artifact-register.sh +7 -1
- package/runtime/claude/bin/artifact-writeback.sh +1 -1
- package/runtime/claude/bin/artifacts.sh +66 -265
- package/runtime/claude/bin/attendant.sh +4 -1
- package/runtime/claude/bin/boundary-guard.cjs +1848 -0
- package/runtime/claude/bin/boundary-hook.sh +41 -0
- package/runtime/claude/bin/boundary.sh +15 -155
- package/runtime/claude/bin/capture-run.sh +32 -2
- package/runtime/claude/bin/codex-render-spec.mjs +24 -46
- package/runtime/claude/bin/codex-session-start.sh +19 -0
- package/runtime/claude/bin/codex-skill-render.mjs +18 -0
- package/runtime/claude/bin/config-get.sh +10 -1
- package/runtime/claude/bin/connect-refresh.sh +16 -0
- package/runtime/claude/bin/corpus_map.py +5 -0
- package/runtime/claude/bin/data/capabilities/claude.json +1480 -0
- package/runtime/claude/bin/genui.sh +19 -0
- package/runtime/claude/bin/graph-batch.sh +1 -1
- package/runtime/claude/bin/graph-op.sh +14 -0
- package/runtime/claude/bin/graph.sh +1 -1
- package/runtime/claude/bin/handoff-pr-backfill.sh +1 -1
- package/runtime/claude/bin/handoff-run.sh +11 -6
- package/runtime/claude/bin/ingest-item.sh +1 -1
- package/runtime/claude/bin/ingest-surface.html +22 -4
- package/runtime/claude/bin/issue.sh +1 -1
- package/runtime/claude/bin/knowledge.sh +1 -1
- package/runtime/claude/bin/lib/card-delivery.sh +329 -0
- package/runtime/claude/bin/lib/config.sh +148 -3
- package/runtime/claude/bin/lib/context.sh +38 -67
- package/runtime/claude/bin/lib/genui-entry.py +36 -0
- package/runtime/claude/bin/lib/git-message.sh +25 -6
- package/runtime/claude/bin/lib/git-sync.sh +12 -4
- package/runtime/claude/bin/lib/greeting.sh +255 -28
- package/runtime/claude/bin/lib/handoff_meta.py +294 -0
- package/runtime/claude/bin/lib/health-footer.sh +83 -2
- package/runtime/claude/bin/lib/identity.sh +31 -10
- package/runtime/claude/bin/lib/instance-registry.sh +71 -0
- package/runtime/claude/bin/lib/local_ci_receipt.py +249 -0
- package/runtime/claude/bin/lib/metrics.sh +121 -8
- package/runtime/claude/bin/lib/notices.sh +36 -0
- package/runtime/claude/bin/lib/record_outcomes.py +729 -0
- package/runtime/claude/bin/lib/reflexes.py +267 -0
- package/runtime/claude/bin/lib/retrieval_scorecard.py +624 -0
- package/runtime/claude/bin/lib/session-record-prompt.txt +18 -0
- package/runtime/claude/bin/lib/session_record.py +3747 -0
- package/runtime/claude/bin/lib/session_trail.py +1983 -0
- package/runtime/claude/bin/lib/settings-drift.sh +8 -5
- package/runtime/claude/bin/lib/startup-restart.sh +41 -0
- package/runtime/claude/bin/lib/worktree-links.sh +92 -0
- package/runtime/claude/bin/memory-push.sh +6 -0
- package/runtime/claude/bin/merge-gate.sh +498 -0
- package/runtime/claude/bin/notify.sh +5 -2
- package/runtime/claude/bin/page-access.sh +103 -0
- package/runtime/claude/bin/person.sh +13 -2
- package/runtime/claude/bin/pi-render-spec.mjs +0 -1
- package/runtime/claude/bin/pi-spirit-discovery.mjs +34 -0
- package/runtime/claude/bin/post-update.sh +181 -0
- package/runtime/claude/bin/prime-render-spec.mjs +0 -1
- package/runtime/claude/bin/publish-artifact.sh +89 -42
- package/runtime/claude/bin/qa-inventory.sh +2 -2
- package/runtime/claude/bin/quest.sh +1 -1
- package/runtime/claude/bin/records.sh +11 -0
- package/runtime/claude/bin/reflexes/05-vitals.sh +9 -0
- package/runtime/claude/bin/reflexes/10-open-items.sh +12 -0
- package/runtime/claude/bin/reflexes/15-retrieval.sh +8 -0
- package/runtime/claude/bin/reflexes/20-nightly.sh +100 -0
- package/runtime/claude/bin/reflexes/20-sandbox.sh +85 -0
- package/runtime/claude/bin/reflexes/30-cost.py +116 -0
- package/runtime/claude/bin/reflexes/40-shells.py +51 -0
- package/runtime/claude/bin/reflexes/README.md +61 -0
- package/runtime/claude/bin/reflexes.sh +7 -0
- package/runtime/claude/bin/runtime-identity.sh +140 -42
- package/runtime/claude/bin/screenshot.sh +77 -0
- package/runtime/claude/bin/scroll.sh +1 -1
- package/runtime/claude/bin/search.sh +31 -5
- package/runtime/claude/bin/session-autosave.sh +6 -3
- package/runtime/claude/bin/session-id.sh +147 -0
- package/runtime/claude/bin/session-log.sh +19 -0
- package/runtime/claude/bin/session-record.sh +26 -0
- package/runtime/claude/bin/session-start.sh +121 -151
- package/runtime/claude/bin/settings.sh +13 -8
- package/runtime/claude/bin/spirit-discovery-claude.sh +4 -0
- package/runtime/claude/bin/spirit-discovery-codex.sh +4 -0
- package/runtime/claude/bin/spirit-discovery-pi.sh +4 -0
- package/runtime/claude/bin/spirit-discovery-prime.sh +4 -0
- package/runtime/claude/bin/spirit-discovery.sh +4 -0
- package/runtime/claude/bin/startup-check.sh +1 -1
- package/runtime/claude/bin/telemetry.sh +11 -4
- package/runtime/claude/bin/test-baseline.sh +33 -5
- package/runtime/claude/bin/todo.sh +1 -1
- package/runtime/claude/bin/worktree-create.sh +15 -45
- package/runtime/claude/bin/worktree.sh +6 -0
- package/runtime/claude/docs/spirit-discovery.md +96 -0
- package/runtime/claude/egregore_runtime/__init__.py +18 -12
- package/runtime/claude/egregore_runtime/access.py +608 -0
- package/runtime/claude/egregore_runtime/access_cli.py +190 -0
- package/runtime/claude/egregore_runtime/adapters/invitation.py +5 -3
- package/runtime/claude/egregore_runtime/adapters/qmd.py +521 -47
- package/runtime/claude/egregore_runtime/admin_entry.py +56 -0
- package/runtime/claude/egregore_runtime/artifacts.py +222 -16
- package/runtime/claude/egregore_runtime/contracts.py +27 -0
- package/runtime/claude/egregore_runtime/eligibility.py +5 -12
- package/runtime/claude/egregore_runtime/episode_binding.py +16 -5
- package/runtime/claude/egregore_runtime/github_issues.py +2 -2
- package/runtime/claude/egregore_runtime/handoff_lookup.py +9 -20
- package/runtime/claude/egregore_runtime/harness_cli.py +674 -149
- package/runtime/claude/egregore_runtime/held.py +2436 -0
- package/runtime/claude/egregore_runtime/hosted_api.py +137 -0
- package/runtime/claude/egregore_runtime/identity.py +196 -65
- package/runtime/claude/egregore_runtime/identity_transfer.py +6 -2
- package/runtime/claude/egregore_runtime/ingest_cli.py +2 -2
- package/runtime/claude/egregore_runtime/ingest_index_cli.py +4 -16
- package/runtime/claude/egregore_runtime/installation.py +119 -0
- package/runtime/claude/egregore_runtime/investigation.py +64 -41
- package/runtime/claude/egregore_runtime/investigation_guidance.py +16 -11
- package/runtime/claude/egregore_runtime/issue_cli.py +51 -32
- package/runtime/claude/egregore_runtime/issue_reporting.py +4 -3
- package/runtime/claude/egregore_runtime/issue_triage_service.py +2 -2
- package/runtime/claude/egregore_runtime/issues.py +12 -6
- package/runtime/claude/egregore_runtime/knowledge.py +331 -5
- package/runtime/claude/egregore_runtime/knowledge_cli.py +57 -17
- package/runtime/claude/egregore_runtime/lifecycle.py +88 -22
- package/runtime/claude/egregore_runtime/lifecycle_cli.py +21 -13
- package/runtime/claude/egregore_runtime/line_ids.py +10 -0
- package/runtime/claude/egregore_runtime/memory_push.py +349 -0
- package/runtime/claude/egregore_runtime/observe.py +64 -25
- package/runtime/claude/egregore_runtime/observers.py +19 -0
- package/runtime/claude/egregore_runtime/optional_modules.py +13 -0
- package/runtime/claude/egregore_runtime/outputs_cli.py +321 -0
- package/runtime/claude/egregore_runtime/policy.py +651 -24
- package/runtime/claude/egregore_runtime/private_result.py +2 -1
- package/runtime/claude/egregore_runtime/projection.py +946 -0
- package/runtime/claude/egregore_runtime/quest_cli.py +20 -23
- package/runtime/claude/egregore_runtime/question_cli.py +15 -11
- package/runtime/claude/egregore_runtime/questions.py +10 -10
- package/runtime/claude/egregore_runtime/quests.py +10 -10
- package/runtime/claude/egregore_runtime/record_publish.py +548 -0
- package/runtime/claude/egregore_runtime/record_subject.py +564 -0
- package/runtime/claude/egregore_runtime/research_ingest.py +2 -4
- package/runtime/claude/egregore_runtime/runtime.py +169 -60
- package/runtime/claude/egregore_runtime/sandbox.py +194 -0
- package/runtime/claude/egregore_runtime/scroll_cli.py +22 -32
- package/runtime/claude/egregore_runtime/scrolls.py +23 -15
- package/runtime/claude/egregore_runtime/shadow.py +262 -0
- package/runtime/claude/egregore_runtime/spirit_discovery.py +487 -0
- package/runtime/claude/egregore_runtime/spirit_discovery_cli.py +25 -0
- package/runtime/claude/egregore_runtime/spirit_discovery_transport.py +18 -0
- package/runtime/claude/egregore_runtime/status.py +101 -44
- package/runtime/claude/egregore_runtime/status_cli.py +13 -5
- package/runtime/claude/egregore_runtime/sync.py +39 -21
- package/runtime/claude/egregore_runtime/taste.py +934 -0
- package/runtime/claude/egregore_runtime/taste_cli.py +242 -0
- package/runtime/claude/egregore_runtime/taste_rules.py +91 -0
- package/runtime/claude/egregore_runtime/telemetry.py +26 -3
- package/runtime/claude/egregore_runtime/todo_cli.py +15 -12
- package/runtime/claude/egregore_runtime/todos.py +3 -2
- package/runtime/claude/egregore_runtime/upgrade.py +85 -58
- package/runtime/claude/egregore_runtime/writeback.py +330 -24
- package/runtime/claude/manifest.json +65 -5
- package/runtime/codex/.claude/context/handoff-claiming.md +32 -0
- package/runtime/codex/.claude/context/notification-consent.md +12 -9
- package/runtime/codex/.claude/skills/view/SKILL.md +438 -0
- package/runtime/codex/.codex/hooks.json +11 -0
- package/runtime/codex/.codex/skills/dark-mode/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/records/SKILL.md +29 -0
- package/runtime/codex/.codex/skills/summon/SKILL.md +1 -1
- package/runtime/codex/.codex/skills/view/assets/compose-scaffold.html +19 -13
- package/runtime/codex/.codex/spec-manifest.json +13 -19
- package/runtime/codex/AGENTS.md +54 -105
- package/runtime/codex/bin/agent.sh +231 -7
- package/runtime/codex/bin/api-call.sh +44 -23
- package/runtime/codex/bin/artifact-register.sh +7 -1
- package/runtime/codex/bin/artifact-writeback.sh +1 -1
- package/runtime/codex/bin/artifacts.sh +66 -265
- package/runtime/codex/bin/attendant.sh +4 -1
- package/runtime/codex/bin/boundary-guard.cjs +1848 -0
- package/runtime/codex/bin/boundary-hook.sh +41 -0
- package/runtime/codex/bin/capture-run.sh +32 -2
- package/runtime/codex/bin/codex-render-spec.mjs +24 -46
- package/runtime/codex/bin/codex-session-start.sh +19 -0
- package/runtime/codex/bin/codex-skill-render.mjs +18 -0
- package/runtime/codex/bin/config-get.sh +10 -1
- package/runtime/codex/bin/connect-refresh.sh +16 -0
- package/runtime/codex/bin/corpus_map.py +5 -0
- package/runtime/codex/bin/data/capabilities/codex.json +1458 -0
- package/runtime/codex/bin/genui.sh +19 -0
- package/runtime/codex/bin/graph-batch.sh +1 -1
- package/runtime/codex/bin/graph-op.sh +14 -0
- package/runtime/codex/bin/graph.sh +1 -1
- package/runtime/codex/bin/handoff-pr-backfill.sh +1 -1
- package/runtime/codex/bin/handoff-run.sh +11 -6
- package/runtime/codex/bin/ingest-item.sh +1 -1
- package/runtime/codex/bin/ingest-surface.html +22 -4
- package/runtime/codex/bin/issue.sh +1 -1
- package/runtime/codex/bin/knowledge.sh +1 -1
- package/runtime/codex/bin/lib/card-delivery.sh +329 -0
- package/runtime/codex/bin/lib/config.sh +148 -3
- package/runtime/codex/bin/lib/context.sh +38 -67
- package/runtime/codex/bin/lib/genui-entry.py +36 -0
- package/runtime/codex/bin/lib/git-message.sh +25 -6
- package/runtime/codex/bin/lib/git-sync.sh +12 -4
- package/runtime/codex/bin/lib/greeting.sh +255 -28
- package/runtime/codex/bin/lib/handoff_meta.py +294 -0
- package/runtime/codex/bin/lib/health-footer.sh +83 -2
- package/runtime/codex/bin/lib/identity.sh +31 -10
- package/runtime/codex/bin/lib/instance-registry.sh +71 -0
- package/runtime/codex/bin/lib/local_ci_receipt.py +249 -0
- package/runtime/codex/bin/lib/metrics.sh +121 -8
- package/runtime/codex/bin/lib/notices.sh +36 -0
- package/runtime/codex/bin/lib/record_outcomes.py +729 -0
- package/runtime/codex/bin/lib/reflexes.py +267 -0
- package/runtime/codex/bin/lib/retrieval_scorecard.py +624 -0
- package/runtime/codex/bin/lib/session-record-prompt.txt +18 -0
- package/runtime/codex/bin/lib/session_record.py +3747 -0
- package/runtime/codex/bin/lib/session_trail.py +1983 -0
- package/runtime/codex/bin/lib/settings-drift.sh +8 -5
- package/runtime/codex/bin/lib/startup-restart.sh +41 -0
- package/runtime/codex/bin/lib/worktree-links.sh +92 -0
- package/runtime/codex/bin/memory-push.sh +6 -0
- package/runtime/codex/bin/merge-gate.sh +498 -0
- package/runtime/codex/bin/notify.sh +5 -2
- package/runtime/codex/bin/page-access.sh +103 -0
- package/runtime/codex/bin/person.sh +13 -2
- package/runtime/codex/bin/pi-render-spec.mjs +0 -1
- package/runtime/codex/bin/pi-spirit-discovery.mjs +34 -0
- package/runtime/codex/bin/post-update.sh +181 -0
- package/runtime/codex/bin/prime-render-spec.mjs +0 -1
- package/runtime/codex/bin/publish-artifact.sh +89 -42
- package/runtime/codex/bin/qa-inventory.sh +2 -2
- package/runtime/codex/bin/quest.sh +1 -1
- package/runtime/codex/bin/records.sh +11 -0
- package/runtime/codex/bin/reflexes/05-vitals.sh +9 -0
- package/runtime/codex/bin/reflexes/10-open-items.sh +12 -0
- package/runtime/codex/bin/reflexes/15-retrieval.sh +8 -0
- package/runtime/codex/bin/reflexes/20-nightly.sh +100 -0
- package/runtime/codex/bin/reflexes/20-sandbox.sh +85 -0
- package/runtime/codex/bin/reflexes/30-cost.py +116 -0
- package/runtime/codex/bin/reflexes/40-shells.py +51 -0
- package/runtime/codex/bin/reflexes/README.md +61 -0
- package/runtime/codex/bin/reflexes.sh +7 -0
- package/runtime/codex/bin/runtime-identity.sh +140 -42
- package/runtime/codex/bin/screenshot.sh +77 -0
- package/runtime/codex/bin/scroll.sh +1 -1
- package/runtime/codex/bin/search.sh +31 -5
- package/runtime/codex/bin/session-autosave.sh +6 -3
- package/runtime/codex/bin/session-id.sh +147 -0
- package/runtime/codex/bin/session-log.sh +19 -0
- package/runtime/codex/bin/session-record.sh +26 -0
- package/runtime/codex/bin/settings.sh +13 -8
- package/runtime/codex/bin/spirit-discovery-claude.sh +4 -0
- package/runtime/codex/bin/spirit-discovery-codex.sh +4 -0
- package/runtime/codex/bin/spirit-discovery-pi.sh +4 -0
- package/runtime/codex/bin/spirit-discovery-prime.sh +4 -0
- package/runtime/codex/bin/spirit-discovery.sh +4 -0
- package/runtime/codex/bin/startup-check.sh +1 -1
- package/runtime/codex/bin/telemetry.sh +11 -4
- package/runtime/codex/bin/test-baseline.sh +33 -5
- package/runtime/codex/bin/todo.sh +1 -1
- package/runtime/codex/bin/worktree-create.sh +15 -45
- package/runtime/codex/bin/worktree.sh +6 -0
- package/runtime/codex/docs/spirit-discovery.md +96 -0
- package/runtime/codex/egregore_runtime/__init__.py +18 -12
- package/runtime/codex/egregore_runtime/access.py +608 -0
- package/runtime/codex/egregore_runtime/access_cli.py +190 -0
- package/runtime/codex/egregore_runtime/adapters/invitation.py +5 -3
- package/runtime/codex/egregore_runtime/adapters/qmd.py +521 -47
- package/runtime/codex/egregore_runtime/admin_entry.py +56 -0
- package/runtime/codex/egregore_runtime/artifacts.py +222 -16
- package/runtime/codex/egregore_runtime/contracts.py +27 -0
- package/runtime/codex/egregore_runtime/eligibility.py +5 -12
- package/runtime/codex/egregore_runtime/episode_binding.py +16 -5
- package/runtime/codex/egregore_runtime/github_issues.py +2 -2
- package/runtime/codex/egregore_runtime/handoff_lookup.py +9 -20
- package/runtime/codex/egregore_runtime/harness_cli.py +674 -149
- package/runtime/codex/egregore_runtime/held.py +2436 -0
- package/runtime/codex/egregore_runtime/hosted_api.py +137 -0
- package/runtime/codex/egregore_runtime/identity.py +196 -65
- package/runtime/codex/egregore_runtime/identity_transfer.py +6 -2
- package/runtime/codex/egregore_runtime/ingest_cli.py +2 -2
- package/runtime/codex/egregore_runtime/ingest_index_cli.py +4 -16
- package/runtime/codex/egregore_runtime/installation.py +119 -0
- package/runtime/codex/egregore_runtime/investigation.py +64 -41
- package/runtime/codex/egregore_runtime/investigation_guidance.py +16 -11
- package/runtime/codex/egregore_runtime/issue_cli.py +51 -32
- package/runtime/codex/egregore_runtime/issue_reporting.py +4 -3
- package/runtime/codex/egregore_runtime/issue_triage_service.py +2 -2
- package/runtime/codex/egregore_runtime/issues.py +12 -6
- package/runtime/codex/egregore_runtime/knowledge.py +331 -5
- package/runtime/codex/egregore_runtime/knowledge_cli.py +57 -17
- package/runtime/codex/egregore_runtime/lifecycle.py +88 -22
- package/runtime/codex/egregore_runtime/lifecycle_cli.py +21 -13
- package/runtime/codex/egregore_runtime/line_ids.py +10 -0
- package/runtime/codex/egregore_runtime/memory_push.py +349 -0
- package/runtime/codex/egregore_runtime/observe.py +64 -25
- package/runtime/codex/egregore_runtime/observers.py +19 -0
- package/runtime/codex/egregore_runtime/optional_modules.py +13 -0
- package/runtime/codex/egregore_runtime/outputs_cli.py +321 -0
- package/runtime/codex/egregore_runtime/policy.py +651 -24
- package/runtime/codex/egregore_runtime/private_result.py +2 -1
- package/runtime/codex/egregore_runtime/projection.py +946 -0
- package/runtime/codex/egregore_runtime/quest_cli.py +20 -23
- package/runtime/codex/egregore_runtime/question_cli.py +15 -11
- package/runtime/codex/egregore_runtime/questions.py +10 -10
- package/runtime/codex/egregore_runtime/quests.py +10 -10
- package/runtime/codex/egregore_runtime/record_publish.py +548 -0
- package/runtime/codex/egregore_runtime/record_subject.py +564 -0
- package/runtime/codex/egregore_runtime/research_ingest.py +2 -4
- package/runtime/codex/egregore_runtime/runtime.py +169 -60
- package/runtime/codex/egregore_runtime/sandbox.py +194 -0
- package/runtime/codex/egregore_runtime/scroll_cli.py +22 -32
- package/runtime/codex/egregore_runtime/scrolls.py +23 -15
- package/runtime/codex/egregore_runtime/shadow.py +262 -0
- package/runtime/codex/egregore_runtime/spirit_discovery.py +487 -0
- package/runtime/codex/egregore_runtime/spirit_discovery_cli.py +25 -0
- package/runtime/codex/egregore_runtime/spirit_discovery_transport.py +18 -0
- package/runtime/codex/egregore_runtime/status.py +101 -44
- package/runtime/codex/egregore_runtime/status_cli.py +13 -5
- package/runtime/codex/egregore_runtime/sync.py +39 -21
- package/runtime/codex/egregore_runtime/taste.py +934 -0
- package/runtime/codex/egregore_runtime/taste_cli.py +242 -0
- package/runtime/codex/egregore_runtime/taste_rules.py +91 -0
- package/runtime/codex/egregore_runtime/telemetry.py +26 -3
- package/runtime/codex/egregore_runtime/todo_cli.py +15 -12
- package/runtime/codex/egregore_runtime/todos.py +3 -2
- package/runtime/codex/egregore_runtime/upgrade.py +85 -58
- package/runtime/codex/egregore_runtime/writeback.py +330 -24
- package/runtime/codex/manifest.json +66 -5
- package/runtime/pi/.claude/context/handoff-claiming.md +32 -0
- package/runtime/pi/.claude/context/notification-consent.md +12 -9
- package/runtime/pi/.claude/skills/view/SKILL.md +438 -0
- package/runtime/pi/.codex/hooks.json +11 -0
- package/runtime/pi/.codex/skills/dark-mode/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/records/SKILL.md +29 -0
- package/runtime/pi/.codex/skills/summon/SKILL.md +1 -1
- package/runtime/pi/.codex/skills/view/assets/compose-scaffold.html +19 -13
- package/runtime/pi/.codex/spec-manifest.json +13 -19
- package/runtime/pi/.pi/APPEND_SYSTEM.md +53 -99
- package/runtime/pi/.pi/extensions/egregore.ts +13 -1
- package/runtime/pi/.pi/spec-manifest.json +2 -2
- package/runtime/pi/AGENTS.md +54 -105
- package/runtime/pi/bin/agent.sh +231 -7
- package/runtime/pi/bin/api-call.sh +44 -23
- package/runtime/pi/bin/artifact-register.sh +7 -1
- package/runtime/pi/bin/artifact-writeback.sh +1 -1
- package/runtime/pi/bin/artifacts.sh +66 -265
- package/runtime/pi/bin/attendant.sh +4 -1
- package/runtime/pi/bin/boundary-guard.cjs +1848 -0
- package/runtime/pi/bin/boundary-hook.sh +41 -0
- package/runtime/pi/bin/capture-run.sh +32 -2
- package/runtime/pi/bin/codex-render-spec.mjs +24 -46
- package/runtime/pi/bin/codex-session-start.sh +19 -0
- package/runtime/pi/bin/codex-skill-render.mjs +18 -0
- package/runtime/pi/bin/config-get.sh +10 -1
- package/runtime/pi/bin/connect-refresh.sh +16 -0
- package/runtime/pi/bin/corpus_map.py +5 -0
- package/runtime/pi/bin/data/capabilities/pi.json +1458 -0
- package/runtime/pi/bin/genui.sh +19 -0
- package/runtime/pi/bin/graph-batch.sh +1 -1
- package/runtime/pi/bin/graph-op.sh +14 -0
- package/runtime/pi/bin/graph.sh +1 -1
- package/runtime/pi/bin/handoff-pr-backfill.sh +1 -1
- package/runtime/pi/bin/handoff-run.sh +11 -6
- package/runtime/pi/bin/ingest-item.sh +1 -1
- package/runtime/pi/bin/ingest-surface.html +22 -4
- package/runtime/pi/bin/issue.sh +1 -1
- package/runtime/pi/bin/knowledge.sh +1 -1
- package/runtime/pi/bin/lib/card-delivery.sh +329 -0
- package/runtime/pi/bin/lib/config.sh +148 -3
- package/runtime/pi/bin/lib/context.sh +38 -67
- package/runtime/pi/bin/lib/genui-entry.py +36 -0
- package/runtime/pi/bin/lib/git-message.sh +25 -6
- package/runtime/pi/bin/lib/git-sync.sh +12 -4
- package/runtime/pi/bin/lib/greeting.sh +255 -28
- package/runtime/pi/bin/lib/handoff_meta.py +294 -0
- package/runtime/pi/bin/lib/health-footer.sh +83 -2
- package/runtime/pi/bin/lib/identity.sh +31 -10
- package/runtime/pi/bin/lib/instance-registry.sh +71 -0
- package/runtime/pi/bin/lib/local_ci_receipt.py +249 -0
- package/runtime/pi/bin/lib/metrics.sh +121 -8
- package/runtime/pi/bin/lib/notices.sh +36 -0
- package/runtime/pi/bin/lib/record_outcomes.py +729 -0
- package/runtime/pi/bin/lib/reflexes.py +267 -0
- package/runtime/pi/bin/lib/retrieval_scorecard.py +624 -0
- package/runtime/pi/bin/lib/session-record-prompt.txt +18 -0
- package/runtime/pi/bin/lib/session_record.py +3747 -0
- package/runtime/pi/bin/lib/session_trail.py +1983 -0
- package/runtime/pi/bin/lib/settings-drift.sh +8 -5
- package/runtime/pi/bin/lib/startup-restart.sh +41 -0
- package/runtime/pi/bin/lib/worktree-links.sh +92 -0
- package/runtime/pi/bin/memory-push.sh +6 -0
- package/runtime/pi/bin/merge-gate.sh +498 -0
- package/runtime/pi/bin/notify.sh +5 -2
- package/runtime/pi/bin/page-access.sh +103 -0
- package/runtime/pi/bin/person.sh +13 -2
- package/runtime/pi/bin/pi-branch-policy.mjs +37 -0
- package/runtime/pi/bin/pi-observe-context.mjs +97 -4
- package/runtime/pi/bin/pi-render-spec.mjs +0 -1
- package/runtime/pi/bin/pi-session-start.sh +4 -1
- package/runtime/pi/bin/pi-spirit-discovery.mjs +34 -0
- package/runtime/pi/bin/post-update.sh +181 -0
- package/runtime/pi/bin/prime-render-spec.mjs +0 -1
- package/runtime/pi/bin/publish-artifact.sh +89 -42
- package/runtime/pi/bin/qa-inventory.sh +2 -2
- package/runtime/pi/bin/quest.sh +1 -1
- package/runtime/pi/bin/records.sh +11 -0
- package/runtime/pi/bin/reflexes/05-vitals.sh +9 -0
- package/runtime/pi/bin/reflexes/10-open-items.sh +12 -0
- package/runtime/pi/bin/reflexes/15-retrieval.sh +8 -0
- package/runtime/pi/bin/reflexes/20-nightly.sh +100 -0
- package/runtime/pi/bin/reflexes/20-sandbox.sh +85 -0
- package/runtime/pi/bin/reflexes/30-cost.py +116 -0
- package/runtime/pi/bin/reflexes/40-shells.py +51 -0
- package/runtime/pi/bin/reflexes/README.md +61 -0
- package/runtime/pi/bin/reflexes.sh +7 -0
- package/runtime/pi/bin/runtime-identity.sh +140 -42
- package/runtime/pi/bin/screenshot.sh +77 -0
- package/runtime/pi/bin/scroll.sh +1 -1
- package/runtime/pi/bin/search.sh +31 -5
- package/runtime/pi/bin/session-autosave.sh +6 -3
- package/runtime/pi/bin/session-id.sh +147 -0
- package/runtime/pi/bin/session-log.sh +19 -0
- package/runtime/pi/bin/session-record.sh +26 -0
- package/runtime/pi/bin/settings.sh +13 -8
- package/runtime/pi/bin/spirit-discovery-claude.sh +4 -0
- package/runtime/pi/bin/spirit-discovery-codex.sh +4 -0
- package/runtime/pi/bin/spirit-discovery-pi.sh +4 -0
- package/runtime/pi/bin/spirit-discovery-prime.sh +4 -0
- package/runtime/pi/bin/spirit-discovery.sh +4 -0
- package/runtime/pi/bin/startup-check.sh +1 -1
- package/runtime/pi/bin/telemetry.sh +11 -4
- package/runtime/pi/bin/test-baseline.sh +33 -5
- package/runtime/pi/bin/todo.sh +1 -1
- package/runtime/pi/bin/worktree-create.sh +15 -45
- package/runtime/pi/bin/worktree.sh +6 -0
- package/runtime/pi/docs/spirit-discovery.md +96 -0
- package/runtime/pi/egregore_runtime/__init__.py +18 -12
- package/runtime/pi/egregore_runtime/access.py +608 -0
- package/runtime/pi/egregore_runtime/access_cli.py +190 -0
- package/runtime/pi/egregore_runtime/adapters/invitation.py +5 -3
- package/runtime/pi/egregore_runtime/adapters/qmd.py +521 -47
- package/runtime/pi/egregore_runtime/admin_entry.py +56 -0
- package/runtime/pi/egregore_runtime/artifacts.py +222 -16
- package/runtime/pi/egregore_runtime/contracts.py +27 -0
- package/runtime/pi/egregore_runtime/eligibility.py +5 -12
- package/runtime/pi/egregore_runtime/episode_binding.py +16 -5
- package/runtime/pi/egregore_runtime/github_issues.py +2 -2
- package/runtime/pi/egregore_runtime/handoff_lookup.py +9 -20
- package/runtime/pi/egregore_runtime/harness_cli.py +674 -149
- package/runtime/pi/egregore_runtime/held.py +2436 -0
- package/runtime/pi/egregore_runtime/hosted_api.py +137 -0
- package/runtime/pi/egregore_runtime/identity.py +196 -65
- package/runtime/pi/egregore_runtime/identity_transfer.py +6 -2
- package/runtime/pi/egregore_runtime/ingest_cli.py +2 -2
- package/runtime/pi/egregore_runtime/ingest_index_cli.py +4 -16
- package/runtime/pi/egregore_runtime/installation.py +119 -0
- package/runtime/pi/egregore_runtime/investigation.py +64 -41
- package/runtime/pi/egregore_runtime/investigation_guidance.py +16 -11
- package/runtime/pi/egregore_runtime/issue_cli.py +51 -32
- package/runtime/pi/egregore_runtime/issue_reporting.py +4 -3
- package/runtime/pi/egregore_runtime/issue_triage_service.py +2 -2
- package/runtime/pi/egregore_runtime/issues.py +12 -6
- package/runtime/pi/egregore_runtime/knowledge.py +331 -5
- package/runtime/pi/egregore_runtime/knowledge_cli.py +57 -17
- package/runtime/pi/egregore_runtime/lifecycle.py +88 -22
- package/runtime/pi/egregore_runtime/lifecycle_cli.py +21 -13
- package/runtime/pi/egregore_runtime/line_ids.py +10 -0
- package/runtime/pi/egregore_runtime/memory_push.py +349 -0
- package/runtime/pi/egregore_runtime/observe.py +64 -25
- package/runtime/pi/egregore_runtime/observers.py +19 -0
- package/runtime/pi/egregore_runtime/optional_modules.py +13 -0
- package/runtime/pi/egregore_runtime/outputs_cli.py +321 -0
- package/runtime/pi/egregore_runtime/policy.py +651 -24
- package/runtime/pi/egregore_runtime/private_result.py +2 -1
- package/runtime/pi/egregore_runtime/projection.py +946 -0
- package/runtime/pi/egregore_runtime/quest_cli.py +20 -23
- package/runtime/pi/egregore_runtime/question_cli.py +15 -11
- package/runtime/pi/egregore_runtime/questions.py +10 -10
- package/runtime/pi/egregore_runtime/quests.py +10 -10
- package/runtime/pi/egregore_runtime/record_publish.py +548 -0
- package/runtime/pi/egregore_runtime/record_subject.py +564 -0
- package/runtime/pi/egregore_runtime/research_ingest.py +2 -4
- package/runtime/pi/egregore_runtime/runtime.py +169 -60
- package/runtime/pi/egregore_runtime/sandbox.py +194 -0
- package/runtime/pi/egregore_runtime/scroll_cli.py +22 -32
- package/runtime/pi/egregore_runtime/scrolls.py +23 -15
- package/runtime/pi/egregore_runtime/shadow.py +262 -0
- package/runtime/pi/egregore_runtime/spirit_discovery.py +487 -0
- package/runtime/pi/egregore_runtime/spirit_discovery_cli.py +25 -0
- package/runtime/pi/egregore_runtime/spirit_discovery_transport.py +18 -0
- package/runtime/pi/egregore_runtime/status.py +101 -44
- package/runtime/pi/egregore_runtime/status_cli.py +13 -5
- package/runtime/pi/egregore_runtime/sync.py +39 -21
- package/runtime/pi/egregore_runtime/taste.py +934 -0
- package/runtime/pi/egregore_runtime/taste_cli.py +242 -0
- package/runtime/pi/egregore_runtime/taste_rules.py +91 -0
- package/runtime/pi/egregore_runtime/telemetry.py +26 -3
- package/runtime/pi/egregore_runtime/todo_cli.py +15 -12
- package/runtime/pi/egregore_runtime/todos.py +3 -2
- package/runtime/pi/egregore_runtime/upgrade.py +85 -58
- package/runtime/pi/egregore_runtime/writeback.py +330 -24
- package/runtime/pi/manifest.json +66 -5
- package/runtime/prime/.claude/context/handoff-claiming.md +32 -0
- package/runtime/prime/.claude/context/notification-consent.md +12 -9
- package/runtime/prime/.claude/skills/view/SKILL.md +438 -0
- package/runtime/prime/.codex/hooks.json +11 -0
- package/runtime/prime/.codex/skills/dark-mode/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/records/SKILL.md +29 -0
- package/runtime/prime/.codex/skills/summon/SKILL.md +1 -1
- package/runtime/prime/.codex/skills/view/assets/compose-scaffold.html +19 -13
- package/runtime/prime/.codex/spec-manifest.json +13 -19
- package/runtime/prime/.prime/agent/APPEND_SYSTEM.md +53 -99
- package/runtime/prime/.prime/agent/extensions/egregore.ts +14 -1
- package/runtime/prime/.prime/agent/skills/egregore/SKILL.md +14 -6
- package/runtime/prime/.prime/agent/skills/egregore/src/egregore/__init__.py +66 -1
- package/runtime/prime/.prime/agent/spec-manifest.json +2 -2
- package/runtime/prime/AGENTS.md +54 -105
- package/runtime/prime/bin/agent.sh +231 -7
- package/runtime/prime/bin/api-call.sh +44 -23
- package/runtime/prime/bin/artifact-register.sh +7 -1
- package/runtime/prime/bin/artifact-writeback.sh +1 -1
- package/runtime/prime/bin/artifacts.sh +66 -265
- package/runtime/prime/bin/attendant.sh +4 -1
- package/runtime/prime/bin/boundary-guard.cjs +1848 -0
- package/runtime/prime/bin/boundary-hook.sh +41 -0
- package/runtime/prime/bin/capture-run.sh +32 -2
- package/runtime/prime/bin/codex-render-spec.mjs +24 -46
- package/runtime/prime/bin/codex-session-start.sh +19 -0
- package/runtime/prime/bin/codex-skill-render.mjs +18 -0
- package/runtime/prime/bin/config-get.sh +10 -1
- package/runtime/prime/bin/connect-refresh.sh +16 -0
- package/runtime/prime/bin/corpus_map.py +5 -0
- package/runtime/prime/bin/data/capabilities/prime.json +1458 -0
- package/runtime/prime/bin/genui.sh +19 -0
- package/runtime/prime/bin/graph-batch.sh +1 -1
- package/runtime/prime/bin/graph-op.sh +14 -0
- package/runtime/prime/bin/graph.sh +1 -1
- package/runtime/prime/bin/handoff-pr-backfill.sh +1 -1
- package/runtime/prime/bin/handoff-run.sh +11 -6
- package/runtime/prime/bin/ingest-item.sh +1 -1
- package/runtime/prime/bin/ingest-surface.html +22 -4
- package/runtime/prime/bin/issue.sh +1 -1
- package/runtime/prime/bin/knowledge.sh +1 -1
- package/runtime/prime/bin/lib/card-delivery.sh +329 -0
- package/runtime/prime/bin/lib/config.sh +148 -3
- package/runtime/prime/bin/lib/context.sh +38 -67
- package/runtime/prime/bin/lib/genui-entry.py +36 -0
- package/runtime/prime/bin/lib/git-message.sh +25 -6
- package/runtime/prime/bin/lib/git-sync.sh +12 -4
- package/runtime/prime/bin/lib/greeting.sh +255 -28
- package/runtime/prime/bin/lib/handoff_meta.py +294 -0
- package/runtime/prime/bin/lib/health-footer.sh +83 -2
- package/runtime/prime/bin/lib/identity.sh +31 -10
- package/runtime/prime/bin/lib/instance-registry.sh +71 -0
- package/runtime/prime/bin/lib/local_ci_receipt.py +249 -0
- package/runtime/prime/bin/lib/metrics.sh +121 -8
- package/runtime/prime/bin/lib/notices.sh +36 -0
- package/runtime/prime/bin/lib/record_outcomes.py +729 -0
- package/runtime/prime/bin/lib/reflexes.py +267 -0
- package/runtime/prime/bin/lib/retrieval_scorecard.py +624 -0
- package/runtime/prime/bin/lib/session-record-prompt.txt +18 -0
- package/runtime/prime/bin/lib/session_record.py +3747 -0
- package/runtime/prime/bin/lib/session_trail.py +1983 -0
- package/runtime/prime/bin/lib/settings-drift.sh +8 -5
- package/runtime/prime/bin/lib/startup-restart.sh +41 -0
- package/runtime/prime/bin/lib/worktree-links.sh +92 -0
- package/runtime/prime/bin/memory-push.sh +6 -0
- package/runtime/prime/bin/merge-gate.sh +498 -0
- package/runtime/prime/bin/notify.sh +5 -2
- package/runtime/prime/bin/page-access.sh +103 -0
- package/runtime/prime/bin/person.sh +13 -2
- package/runtime/prime/bin/pi-branch-policy.mjs +37 -0
- package/runtime/prime/bin/pi-observe-context.mjs +97 -4
- package/runtime/prime/bin/pi-render-spec.mjs +0 -1
- package/runtime/prime/bin/pi-spirit-discovery.mjs +34 -0
- package/runtime/prime/bin/post-update.sh +181 -0
- package/runtime/prime/bin/prime-render-spec.mjs +0 -1
- package/runtime/prime/bin/prime-session-start.sh +3 -1
- package/runtime/prime/bin/publish-artifact.sh +89 -42
- package/runtime/prime/bin/qa-inventory.sh +2 -2
- package/runtime/prime/bin/quest.sh +1 -1
- package/runtime/prime/bin/records.sh +11 -0
- package/runtime/prime/bin/reflexes/05-vitals.sh +9 -0
- package/runtime/prime/bin/reflexes/10-open-items.sh +12 -0
- package/runtime/prime/bin/reflexes/15-retrieval.sh +8 -0
- package/runtime/prime/bin/reflexes/20-nightly.sh +100 -0
- package/runtime/prime/bin/reflexes/20-sandbox.sh +85 -0
- package/runtime/prime/bin/reflexes/30-cost.py +116 -0
- package/runtime/prime/bin/reflexes/40-shells.py +51 -0
- package/runtime/prime/bin/reflexes/README.md +61 -0
- package/runtime/prime/bin/reflexes.sh +7 -0
- package/runtime/prime/bin/runtime-identity.sh +140 -42
- package/runtime/prime/bin/screenshot.sh +77 -0
- package/runtime/prime/bin/scroll.sh +1 -1
- package/runtime/prime/bin/search.sh +31 -5
- package/runtime/prime/bin/session-autosave.sh +6 -3
- package/runtime/prime/bin/session-id.sh +147 -0
- package/runtime/prime/bin/session-log.sh +19 -0
- package/runtime/prime/bin/session-record.sh +26 -0
- package/runtime/prime/bin/settings.sh +13 -8
- package/runtime/prime/bin/spirit-discovery-claude.sh +4 -0
- package/runtime/prime/bin/spirit-discovery-codex.sh +4 -0
- package/runtime/prime/bin/spirit-discovery-pi.sh +4 -0
- package/runtime/prime/bin/spirit-discovery-prime.sh +4 -0
- package/runtime/prime/bin/spirit-discovery.sh +4 -0
- package/runtime/prime/bin/startup-check.sh +1 -1
- package/runtime/prime/bin/telemetry.sh +11 -4
- package/runtime/prime/bin/test-baseline.sh +33 -5
- package/runtime/prime/bin/todo.sh +1 -1
- package/runtime/prime/bin/worktree-create.sh +15 -45
- package/runtime/prime/bin/worktree.sh +6 -0
- package/runtime/prime/docs/spirit-discovery.md +96 -0
- package/runtime/prime/egregore_runtime/__init__.py +18 -12
- package/runtime/prime/egregore_runtime/access.py +608 -0
- package/runtime/prime/egregore_runtime/access_cli.py +190 -0
- package/runtime/prime/egregore_runtime/adapters/invitation.py +5 -3
- package/runtime/prime/egregore_runtime/adapters/qmd.py +521 -47
- package/runtime/prime/egregore_runtime/admin_entry.py +56 -0
- package/runtime/prime/egregore_runtime/artifacts.py +222 -16
- package/runtime/prime/egregore_runtime/contracts.py +27 -0
- package/runtime/prime/egregore_runtime/eligibility.py +5 -12
- package/runtime/prime/egregore_runtime/episode_binding.py +16 -5
- package/runtime/prime/egregore_runtime/github_issues.py +2 -2
- package/runtime/prime/egregore_runtime/handoff_lookup.py +9 -20
- package/runtime/prime/egregore_runtime/harness_cli.py +674 -149
- package/runtime/prime/egregore_runtime/held.py +2436 -0
- package/runtime/prime/egregore_runtime/hosted_api.py +137 -0
- package/runtime/prime/egregore_runtime/identity.py +196 -65
- package/runtime/prime/egregore_runtime/identity_transfer.py +6 -2
- package/runtime/prime/egregore_runtime/ingest_cli.py +2 -2
- package/runtime/prime/egregore_runtime/ingest_index_cli.py +4 -16
- package/runtime/prime/egregore_runtime/installation.py +119 -0
- package/runtime/prime/egregore_runtime/investigation.py +64 -41
- package/runtime/prime/egregore_runtime/investigation_guidance.py +16 -11
- package/runtime/prime/egregore_runtime/issue_cli.py +51 -32
- package/runtime/prime/egregore_runtime/issue_reporting.py +4 -3
- package/runtime/prime/egregore_runtime/issue_triage_service.py +2 -2
- package/runtime/prime/egregore_runtime/issues.py +12 -6
- package/runtime/prime/egregore_runtime/knowledge.py +331 -5
- package/runtime/prime/egregore_runtime/knowledge_cli.py +57 -17
- package/runtime/prime/egregore_runtime/lifecycle.py +88 -22
- package/runtime/prime/egregore_runtime/lifecycle_cli.py +21 -13
- package/runtime/prime/egregore_runtime/line_ids.py +10 -0
- package/runtime/prime/egregore_runtime/memory_push.py +349 -0
- package/runtime/prime/egregore_runtime/observe.py +64 -25
- package/runtime/prime/egregore_runtime/observers.py +19 -0
- package/runtime/prime/egregore_runtime/optional_modules.py +13 -0
- package/runtime/prime/egregore_runtime/outputs_cli.py +321 -0
- package/runtime/prime/egregore_runtime/policy.py +651 -24
- package/runtime/prime/egregore_runtime/private_result.py +2 -1
- package/runtime/prime/egregore_runtime/projection.py +946 -0
- package/runtime/prime/egregore_runtime/quest_cli.py +20 -23
- package/runtime/prime/egregore_runtime/question_cli.py +15 -11
- package/runtime/prime/egregore_runtime/questions.py +10 -10
- package/runtime/prime/egregore_runtime/quests.py +10 -10
- package/runtime/prime/egregore_runtime/record_publish.py +548 -0
- package/runtime/prime/egregore_runtime/record_subject.py +564 -0
- package/runtime/prime/egregore_runtime/research_ingest.py +2 -4
- package/runtime/prime/egregore_runtime/runtime.py +169 -60
- package/runtime/prime/egregore_runtime/sandbox.py +194 -0
- package/runtime/prime/egregore_runtime/scroll_cli.py +22 -32
- package/runtime/prime/egregore_runtime/scrolls.py +23 -15
- package/runtime/prime/egregore_runtime/shadow.py +262 -0
- package/runtime/prime/egregore_runtime/spirit_discovery.py +487 -0
- package/runtime/prime/egregore_runtime/spirit_discovery_cli.py +25 -0
- package/runtime/prime/egregore_runtime/spirit_discovery_transport.py +18 -0
- package/runtime/prime/egregore_runtime/status.py +101 -44
- package/runtime/prime/egregore_runtime/status_cli.py +13 -5
- package/runtime/prime/egregore_runtime/sync.py +39 -21
- package/runtime/prime/egregore_runtime/taste.py +934 -0
- package/runtime/prime/egregore_runtime/taste_cli.py +242 -0
- package/runtime/prime/egregore_runtime/taste_rules.py +91 -0
- package/runtime/prime/egregore_runtime/telemetry.py +26 -3
- package/runtime/prime/egregore_runtime/todo_cli.py +15 -12
- package/runtime/prime/egregore_runtime/todos.py +3 -2
- package/runtime/prime/egregore_runtime/upgrade.py +85 -58
- package/runtime/prime/egregore_runtime/writeback.py +330 -24
- package/runtime/prime/manifest.json +66 -5
- package/runtime/claude/bin/thread.sh +0 -9
- package/runtime/claude/egregore_runtime/admin_gate.py +0 -102
- package/runtime/claude/egregore_runtime/thread_cli.py +0 -173
- package/runtime/claude/egregore_runtime/threads.py +0 -521
- package/runtime/codex/bin/thread.sh +0 -9
- package/runtime/codex/egregore_runtime/admin_gate.py +0 -102
- package/runtime/codex/egregore_runtime/thread_cli.py +0 -173
- package/runtime/codex/egregore_runtime/threads.py +0 -521
- package/runtime/pi/bin/thread.sh +0 -9
- package/runtime/pi/egregore_runtime/admin_gate.py +0 -102
- package/runtime/pi/egregore_runtime/thread_cli.py +0 -173
- package/runtime/pi/egregore_runtime/threads.py +0 -521
- package/runtime/prime/bin/thread.sh +0 -9
- package/runtime/prime/egregore_runtime/admin_gate.py +0 -102
- package/runtime/prime/egregore_runtime/thread_cli.py +0 -173
- package/runtime/prime/egregore_runtime/threads.py +0 -521
|
@@ -0,0 +1,564 @@
|
|
|
1
|
+
"""Pure, public presentation of record subjects. Classification grants no access.
|
|
2
|
+
|
|
3
|
+
Unknown or damaged producer metadata never falls back to a human session. Only
|
|
4
|
+
the fixed, closed metadata facts shape is projected; Markdown prose is not data.
|
|
5
|
+
Callers must authorize the source and mask held records before using this module.
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from collections import Counter
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
import hashlib
|
|
12
|
+
import json
|
|
13
|
+
import math
|
|
14
|
+
import re
|
|
15
|
+
|
|
16
|
+
PRODUCER = "pi-durable.metadata/v1"
|
|
17
|
+
CONTROL = re.compile(r"^spirit-[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$")
|
|
18
|
+
ACTOR = re.compile(r"^[A-Za-z0-9._:-]{1,160}$")
|
|
19
|
+
PRINCIPAL = re.compile(r"^principal:[a-z0-9][a-z0-9_.-]{0,63}$")
|
|
20
|
+
HASH = re.compile(r"^[a-f0-9]{64}$")
|
|
21
|
+
MARKERS = {"producer", "producer_principal_id", "record_owner_actor_id", "producer_identity", "subject_kind",
|
|
22
|
+
"source_projection_version", "source_projection_revision", "source_evidence_revision"}
|
|
23
|
+
STATE = {
|
|
24
|
+
"execution": {"queued", "running", "stopped"},
|
|
25
|
+
"outcome": {"pending", "unverified", "fulfilled", "partial", "needs_input", "blocked", "failed", "cancelled"},
|
|
26
|
+
"verification": {"not_run", "passed", "failed", "inconclusive"},
|
|
27
|
+
"delivery": {"not_requested", "pending", "unknown", "sent", "failed"},
|
|
28
|
+
}
|
|
29
|
+
GAPS = {"attachment_unavailable", "submission_unavailable", "reconciliation_failed", "entry_limit",
|
|
30
|
+
"incomplete_submission", "observer_stopped", "identity_conflict"}
|
|
31
|
+
|
|
32
|
+
OBSERVATION_ENUMS = {
|
|
33
|
+
**STATE,
|
|
34
|
+
"transition": {"admitted", "claimed", "settled", "cancelled", "retried", "answered", "recovered", "delivery"},
|
|
35
|
+
"status": {"queued", "placed", "done", "unanswered"},
|
|
36
|
+
"entry_kind": {"user", "assistant", "tool_result", "other"},
|
|
37
|
+
"tool_status": {"success", "error", "unknown"},
|
|
38
|
+
}
|
|
39
|
+
OBSERVATION_FIELDS = {
|
|
40
|
+
"lifecycle": {"transition", "execution", "outcome", "verification", "delivery", "attempt"},
|
|
41
|
+
"submission": {"status"}, "entry": {"entry_kind", "tool_status"}, "gap": {"reason"},
|
|
42
|
+
}
|
|
43
|
+
OBSERVATION_REQUIRED = {"lifecycle": "transition", "submission": "status", "entry": "entry_kind", "gap": "reason"}
|
|
44
|
+
V2_GAPS = GAPS | {"restart_gap", "queue_overflow", "stream_snapshot", "write_failed", "flush_failed",
|
|
45
|
+
"source_unavailable", "unattributed", "lineage_limit", "usage_unavailable", "usage_reset", "shutdown_incomplete"}
|
|
46
|
+
V2_FIELDS = {
|
|
47
|
+
"task": {"task_kind", "state", "outcome", "parent_ref", "background", "abort_requested"},
|
|
48
|
+
"run": {"phase", "submission_ref"},
|
|
49
|
+
"tool": {"phase", "status", "capability_ref", "call_ref", "task_ref", "parent_call_ref", "parent_task_ref"},
|
|
50
|
+
"retry": {"phase", "attempt"}, "compaction": {"phase", "reason", "task_ref", "blocking", "status"},
|
|
51
|
+
}
|
|
52
|
+
V2_ENUMS = {
|
|
53
|
+
"task_kind": {"run", "generation", "tool", "compaction", "other"},
|
|
54
|
+
"state": {"pending", "running", "waiting", "completing", "terminal"},
|
|
55
|
+
"outcome": {"completed", "failed", "aborted", "orphaned", "faulted", "unknown"},
|
|
56
|
+
"phase": {"start", "end", "snapshot"}, "status": {"success", "error", "unknown"},
|
|
57
|
+
"reason": {"manual", "threshold", "overflow", "unknown"},
|
|
58
|
+
}
|
|
59
|
+
USAGE_COUNTERS = {"model_tokens", "tool_tokens", "model_reported_cost", "tool_reported_cost"}
|
|
60
|
+
|
|
61
|
+
def _int(value, maximum=2**53 - 1):
|
|
62
|
+
return type(value) is int and 0 <= value <= maximum
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _stamp(value):
|
|
66
|
+
try:
|
|
67
|
+
return isinstance(value, str) and len(value) <= 40 and datetime.fromisoformat(value.replace("Z", "+00:00")).tzinfo is not None
|
|
68
|
+
except ValueError:
|
|
69
|
+
return False
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _observation_fields(value, allowed, required=None):
|
|
73
|
+
if not isinstance(value, dict) or not set(value) <= set(allowed) or not set(required if required is not None else allowed) <= set(value):
|
|
74
|
+
raise ValueError("invalid metadata shape")
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _observation_stamp(value):
|
|
78
|
+
if not isinstance(value, str) or len(value) > 40 or not value.endswith(("Z", "+00:00")):
|
|
79
|
+
raise ValueError("invalid metadata time")
|
|
80
|
+
parsed = datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
81
|
+
if parsed.tzinfo is None:
|
|
82
|
+
raise ValueError("invalid metadata time")
|
|
83
|
+
return parsed.timestamp()
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _observation_reference(value, *, nullable=False):
|
|
87
|
+
if nullable and value is None:
|
|
88
|
+
return
|
|
89
|
+
if not isinstance(value, str) or not HASH.fullmatch(value):
|
|
90
|
+
raise ValueError("invalid opaque reference")
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _observation_metadata_v2(kind, metadata):
|
|
94
|
+
if kind in OBSERVATION_FIELDS:
|
|
95
|
+
_observation_fields(metadata, OBSERVATION_FIELDS[kind], {OBSERVATION_REQUIRED[kind]})
|
|
96
|
+
for key, item in metadata.items():
|
|
97
|
+
valid = _int(item) if key == "attempt" else isinstance(item, str) and item in (V2_GAPS if kind == "gap" else OBSERVATION_ENUMS[key])
|
|
98
|
+
if not valid:
|
|
99
|
+
raise ValueError("invalid metadata value")
|
|
100
|
+
return
|
|
101
|
+
if kind not in V2_FIELDS:
|
|
102
|
+
raise ValueError("invalid metadata kind")
|
|
103
|
+
_observation_fields(metadata, V2_FIELDS[kind])
|
|
104
|
+
for key, item in metadata.items():
|
|
105
|
+
if key.endswith("_ref"):
|
|
106
|
+
_observation_reference(item, nullable=True)
|
|
107
|
+
elif key in {"background", "abort_requested", "blocking"}:
|
|
108
|
+
if type(item) is not bool and not (key == "blocking" and item is None):
|
|
109
|
+
raise ValueError("invalid metadata boolean")
|
|
110
|
+
elif key == "attempt":
|
|
111
|
+
if not _int(item):
|
|
112
|
+
raise ValueError("invalid metadata attempt")
|
|
113
|
+
elif not isinstance(item, str) or item not in (V2_ENUMS[key] | {"update"} if kind == "tool" and key == "phase" else V2_ENUMS[key]):
|
|
114
|
+
raise ValueError("invalid metadata enum")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _observation_usage_values(value, *, bound=False):
|
|
118
|
+
_observation_fields(value, USAGE_COUNTERS | ({"ref"} if bound else set()))
|
|
119
|
+
if bound:
|
|
120
|
+
_observation_reference(value["ref"])
|
|
121
|
+
for key in USAGE_COUNTERS:
|
|
122
|
+
item = value[key]
|
|
123
|
+
if item is not None and (not _int(item) if key.endswith("_tokens") else
|
|
124
|
+
type(item) not in {int, float} or not math.isfinite(item) or item < 0):
|
|
125
|
+
raise ValueError("invalid usage counter")
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def validate_spirit_snapshot_v2(snapshot, *, revision):
|
|
129
|
+
"""Pure closed V2 projection validation, with no live freshness or authority.
|
|
130
|
+
|
|
131
|
+
Private capture separately enforces current collection time and permissions;
|
|
132
|
+
public readers validate historical canonical facts without renewing either.
|
|
133
|
+
"""
|
|
134
|
+
_observation_fields(snapshot, {"state", "events", "coverage", "captured_at"})
|
|
135
|
+
state = snapshot["state"]
|
|
136
|
+
_observation_fields(state, {*STATE, "revision", "attempt"})
|
|
137
|
+
if (not _int(revision) or not _int(state["revision"]) or state["revision"] != revision
|
|
138
|
+
or not _int(state["attempt"]) or any(not isinstance(state[key], str) or state[key] not in allowed for key, allowed in STATE.items())):
|
|
139
|
+
raise ValueError("invalid intent state")
|
|
140
|
+
captured = _observation_stamp(snapshot["captured_at"])
|
|
141
|
+
events, coverage = snapshot["events"], snapshot["coverage"]
|
|
142
|
+
if (not isinstance(events, list) or len(events) > 256
|
|
143
|
+
or len(json.dumps(events, separators=(",", ":"), ensure_ascii=False).encode()) > 128 * 1024
|
|
144
|
+
or len(json.dumps(snapshot, separators=(",", ":"), ensure_ascii=False).encode()) > 256 * 1024):
|
|
145
|
+
raise ValueError("invalid metadata count")
|
|
146
|
+
identities = set()
|
|
147
|
+
for event in events:
|
|
148
|
+
_observation_fields(event, {"id", "revision", "source", "source_ref", "source_revision_ref", "epoch_ref", "kind", "metadata", "observed_at", "native_event_time"})
|
|
149
|
+
for key in ("id", "source_ref", "source_revision_ref", "epoch_ref"):
|
|
150
|
+
_observation_reference(event[key], nullable=key == "epoch_ref")
|
|
151
|
+
if (event["id"] in identities or type(event["revision"]) is not int or event["revision"] != revision
|
|
152
|
+
or event["source"] not in {"application", "commit", "semantic", "reconcile"}
|
|
153
|
+
or _observation_stamp(event["observed_at"]) > captured + 2
|
|
154
|
+
or event["native_event_time"] is not None and _observation_stamp(event["native_event_time"]) > captured + 2):
|
|
155
|
+
raise ValueError("invalid metadata event")
|
|
156
|
+
identities.add(event["id"])
|
|
157
|
+
_observation_metadata_v2(event["kind"], event["metadata"])
|
|
158
|
+
_observation_fields(coverage, {"schema", "projection_revision", "historical", "native_journal", "window", "capture", "gaps", "epochs", "usage"})
|
|
159
|
+
if (coverage["schema"] != "egregore.spirit-observation-coverage/v2" or not _int(coverage["projection_revision"])
|
|
160
|
+
or coverage["historical"] != "unknown" or coverage["native_journal"] != "unknown"):
|
|
161
|
+
raise ValueError("invalid coverage")
|
|
162
|
+
window = coverage["window"]
|
|
163
|
+
_observation_fields(window, {"selection", "revision", "limit", "event_count", "earlier", "last_event_ref"})
|
|
164
|
+
if (window["selection"] != "revision_tail" or type(window["revision"]) is not int or window["revision"] != revision
|
|
165
|
+
or type(window["limit"]) is not int or window["limit"] != 256 or not _int(window["event_count"], 256)
|
|
166
|
+
or window["event_count"] != len(events) or window["earlier"] not in {"none", "omitted"}
|
|
167
|
+
or window["last_event_ref"] != (events[-1]["id"] if events else None)):
|
|
168
|
+
raise ValueError("invalid coverage window")
|
|
169
|
+
capture = coverage["capture"]
|
|
170
|
+
_observation_fields(capture, {"durable", "semantic", "application", "unattributed"})
|
|
171
|
+
if (any(capture[key] not in {"captured", "partial", "unknown"} for key in ("durable", "semantic", "application"))
|
|
172
|
+
or capture["unattributed"] != "unknown"):
|
|
173
|
+
raise ValueError("invalid capture coverage")
|
|
174
|
+
gaps = coverage["gaps"]
|
|
175
|
+
_observation_fields(gaps, {"reasons", "dropped_lower_bound", "unmeasured_loss"})
|
|
176
|
+
if (not isinstance(gaps["reasons"], list) or any(not isinstance(item, str) or item not in V2_GAPS for item in gaps["reasons"])
|
|
177
|
+
or gaps["reasons"] != sorted(set(gaps["reasons"])) or not _int(gaps["dropped_lower_bound"])
|
|
178
|
+
or type(gaps["unmeasured_loss"]) is not bool):
|
|
179
|
+
raise ValueError("invalid gap coverage")
|
|
180
|
+
epochs = coverage["epochs"]
|
|
181
|
+
_observation_fields(epochs, {"items", "earlier"})
|
|
182
|
+
if not isinstance(epochs["items"], list) or len(epochs["items"]) > 32 or epochs["earlier"] not in {"none", "omitted"}:
|
|
183
|
+
raise ValueError("invalid epoch coverage")
|
|
184
|
+
seen = set()
|
|
185
|
+
for epoch in epochs["items"]:
|
|
186
|
+
_observation_fields(epoch, {"ref", "conversation_ref", "scope", "state", "opened_at", "closed_at", "first_commit_ref", "last_commit_ref", "last_semantic_ref"})
|
|
187
|
+
for key in ("ref", "conversation_ref", "first_commit_ref", "last_commit_ref", "last_semantic_ref"):
|
|
188
|
+
_observation_reference(epoch[key], nullable=key not in {"ref", "conversation_ref"})
|
|
189
|
+
opened = _observation_stamp(epoch["opened_at"])
|
|
190
|
+
closed = _observation_stamp(epoch["closed_at"]) if epoch["closed_at"] is not None else None
|
|
191
|
+
if (epoch["ref"] in seen or epoch["scope"] not in {"root", "owned_child"} or epoch["state"] not in {"open", "closed", "uncertain"}
|
|
192
|
+
or opened > captured + 2 or closed is not None and (closed < opened or closed > captured + 2)
|
|
193
|
+
or epoch["state"] == "closed" and closed is None or epoch["state"] == "open" and closed is not None):
|
|
194
|
+
raise ValueError("invalid epoch")
|
|
195
|
+
seen.add(epoch["ref"])
|
|
196
|
+
usage = coverage["usage"]
|
|
197
|
+
_observation_fields(usage, {"source", "billing", "items", "earlier"})
|
|
198
|
+
if (usage["source"] != "pi.usage" or usage["billing"] != "unverified" or usage["earlier"] not in {"none", "omitted"}
|
|
199
|
+
or not isinstance(usage["items"], list) or len(usage["items"]) > 16):
|
|
200
|
+
raise ValueError("invalid usage coverage")
|
|
201
|
+
seen = set()
|
|
202
|
+
for row in usage["items"]:
|
|
203
|
+
_observation_fields(row, {"conversation_ref", "scope", "attribution", "start", "end", "delta"})
|
|
204
|
+
_observation_reference(row["conversation_ref"])
|
|
205
|
+
if (row["conversation_ref"] in seen or row["scope"] not in {"root", "owned_child"}
|
|
206
|
+
or row["attribution"] not in {"owned_revision", "conversation_only", "unknown"}):
|
|
207
|
+
raise ValueError("invalid usage ownership")
|
|
208
|
+
seen.add(row["conversation_ref"])
|
|
209
|
+
for key in ("start", "end"):
|
|
210
|
+
if row[key] is not None:
|
|
211
|
+
_observation_usage_values(row[key], bound=True)
|
|
212
|
+
if row["delta"] is not None:
|
|
213
|
+
_observation_usage_values(row["delta"])
|
|
214
|
+
if row["attribution"] != "owned_revision" or row["start"] is None or row["end"] is None:
|
|
215
|
+
raise ValueError("invalid usage attribution")
|
|
216
|
+
for key in USAGE_COUNTERS:
|
|
217
|
+
start, end = row["start"][key], row["end"][key]
|
|
218
|
+
expected = None if start is None or end is None else end - start
|
|
219
|
+
if expected is not None and expected < 0 or row["delta"][key] != expected:
|
|
220
|
+
raise ValueError("invalid usage delta")
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def spirit_snapshot_facts_v2(control_id, revision, snapshot):
|
|
224
|
+
validate_spirit_snapshot_v2(snapshot, revision=revision)
|
|
225
|
+
if not isinstance(control_id, str) or not CONTROL.fullmatch(control_id):
|
|
226
|
+
raise ValueError("invalid intent control")
|
|
227
|
+
events = snapshot["events"]
|
|
228
|
+
return {
|
|
229
|
+
"schema": "egregore.spirit-session-facts/v2", "harness": "pi-durable", "subject_kind": "durable-intent",
|
|
230
|
+
"control_id": control_id, "intents": 1, "current_revision": revision,
|
|
231
|
+
"projection_revision": snapshot["coverage"]["projection_revision"], "state": snapshot["state"],
|
|
232
|
+
"observed_event_count": len(events), "observed_event_counts": {
|
|
233
|
+
"source": dict(sorted(Counter(item["source"] for item in events).items())),
|
|
234
|
+
"kind": dict(sorted(Counter(item["kind"] for item in events).items()))},
|
|
235
|
+
"events": events, "coverage": snapshot["coverage"],
|
|
236
|
+
"observed_through": max((item["observed_at"] for item in events), key=_observation_stamp, default=None),
|
|
237
|
+
"captured_at": snapshot["captured_at"], "cost": None, "duration": None,
|
|
238
|
+
"canonical_transcript": False, "complete_history": False,
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# Public integrity checks only. These closed historical facts neither authenticate
|
|
243
|
+
# their producer nor confer permission to obtain the underlying private sources.
|
|
244
|
+
EVIDENCE_REASONS = {"not_recorded", "source_unavailable", "source_limit", "identity_conflict",
|
|
245
|
+
"stale_revision", "stale_attempt", "pending_capture", "capture_failed", "historical_retention", "unknown_schema"}
|
|
246
|
+
EVIDENCE_DOMAINS = {"generation", "recipe", "capability_lock", "authority", "artifacts", "outcome", "repair", "capture"}
|
|
247
|
+
EVIDENCE_EXTRA = ("evidence", "evidence_history", "observation_fingerprint", "pair_digest")
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _ev_enum(*values):
|
|
251
|
+
return lambda value: isinstance(value, str) and value in values
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def _ev_nullable(check):
|
|
255
|
+
return lambda value: value is None or check(value)
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
def _ev_hash(value):
|
|
259
|
+
return isinstance(value, str) and HASH.fullmatch(value) is not None
|
|
260
|
+
|
|
261
|
+
|
|
262
|
+
def _ev_object(shape):
|
|
263
|
+
return lambda value: (isinstance(value, dict) and set(value) == set(shape)
|
|
264
|
+
and all(check(value[key]) for key, check in shape.items()))
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def _ev_array(check, limit, key=lambda value: value, *, ordered=True):
|
|
268
|
+
def valid(value):
|
|
269
|
+
if not isinstance(value, list) or len(value) > limit or not all(check(item) for item in value):
|
|
270
|
+
return False
|
|
271
|
+
identities = [key(item) for item in value]
|
|
272
|
+
return len(set(identities)) == len(identities) and (not ordered or identities == sorted(identities))
|
|
273
|
+
return valid
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
_EV_REF = _ev_nullable(_ev_hash)
|
|
277
|
+
_EV_DOMAIN = {"status": _ev_enum("retained", "missing", "conflicting", "truncated", "not_applicable"),
|
|
278
|
+
"reason": _ev_nullable(_ev_enum(*EVIDENCE_REASONS))}
|
|
279
|
+
_EV_ERROR = _ev_nullable(_ev_enum("cancelled", "deadline", "lease_lost", "verification_failed", "output_limit", "execution_failed", "unknown"))
|
|
280
|
+
_EV_GENERATION_REFS = ("generation_digest", "descriptor_digest", "source_binding_digest", "source_digest", "registry_digest",
|
|
281
|
+
"policy_digest", "persona_digest", "model_ref", "dependency_digest")
|
|
282
|
+
_EV_RECIPE_REFS = ("binding_digest", "recipe_ref", "recipe_digest", "candidate_subject", "candidate_envelope", "catalog_snapshot",
|
|
283
|
+
"generation_digest", "registry_digest", "policy_digest", "profile_ref", "scope_digest")
|
|
284
|
+
_EV_AUTHORITY_REFS = ("actor_ref", "caller_ref", "grant_ref", "audience_ref", "profile_ref", "origin_ref", "parent_request_ref", "admission_digest", "validator_ref")
|
|
285
|
+
_EV_CHECK = {"check_ref": _ev_hash, "evaluator_ref": _ev_hash, "evaluator_code_digest": _EV_REF,
|
|
286
|
+
"status": _ev_enum("passed", "failed", "inconclusive"), "scope": _ev_enum("artifact_ownership", "artifact_retention", "repair_preparation", "unknown"),
|
|
287
|
+
"basis": _ev_enum("retained_host_check"), "evidence_refs": _ev_array(_ev_hash, 30)}
|
|
288
|
+
_EV_CAPABILITY = {**{key: _ev_hash for key in ("capability_ref", "definition_digest", "package_digest", "recipe_digest", "generation_digest")},
|
|
289
|
+
"adapter_refs": _ev_array(_ev_hash, 16), "tool_refs": _ev_array(_ev_hash, 32), "binding": _ev_enum("admitted_definition")}
|
|
290
|
+
_EV_REPAIR_CHECK = {"check_ref": _ev_hash, **{key: _EV_REF for key in ("command_ref", "manifest_digest", "verifier_digest")},
|
|
291
|
+
"status": _ev_enum("passed", "failed", "inconclusive"), "identity_scope": _ev_enum("declared_files", "configuration_only", "unknown")}
|
|
292
|
+
_EV_REPAIR = {**_EV_DOMAIN, **{key: _EV_REF for key in ("receipt_digest", "task_ref", "accepted_event_ref", "accepted_result_ref",
|
|
293
|
+
"accepted_source_ref", "source_revision_ref", "base_revision_ref", "configuration_digest", "patch_digest")},
|
|
294
|
+
"repair_attempt": _ev_nullable(_int), "lease_epoch": _ev_nullable(_int),
|
|
295
|
+
"accepted_state": _ev_nullable(_ev_enum("review_ready", "failed", "needs_attention")),
|
|
296
|
+
"result_state": _ev_nullable(_ev_enum("ready_for_review", "cancelled", "timed_out", "failed", "needs_attention", "unknown")),
|
|
297
|
+
"verification": _ev_array(_ev_object(_EV_REPAIR_CHECK), 20, lambda row: row["check_ref"], ordered=False),
|
|
298
|
+
"error_class": _EV_ERROR, "evidence_scope": _ev_enum("none", "accepted_repair_preparation"),
|
|
299
|
+
"verification_identity": _ev_enum("retained_declared_files", "configuration_only", "unknown")}
|
|
300
|
+
_EV_ARTIFACT = {"artifact_ref": _ev_hash, "kind": _ev_enum("finding", "proposal", "issue_registration"), "request_ref": _ev_hash,
|
|
301
|
+
**{key: _EV_REF for key in ("source_fingerprint", "task_ref", "capability_ref", "result_ref")},
|
|
302
|
+
"retention": _ev_enum("retained", "missing", "conflicting"), "external_result": _ev_enum("unknown", "accepted_preparation"),
|
|
303
|
+
"check_refs": _ev_array(_ev_hash, 32)}
|
|
304
|
+
_EV_OUTCOME = {**_EV_DOMAIN, "execution": _ev_enum(*STATE["execution"]), "current_state": _ev_enum(*STATE["outcome"]),
|
|
305
|
+
**{key: _EV_REF for key in ("report_ref", "acceptance_contract_digest", "validator_ref")},
|
|
306
|
+
"reported_state": _ev_nullable(_ev_enum("fulfilled", "partial", "needs_input", "blocked", "failed")),
|
|
307
|
+
"reported_evidence_refs": _ev_array(_ev_hash, 30), "resolved_state": _ev_nullable(_ev_enum(*STATE["outcome"])),
|
|
308
|
+
"verification": _ev_enum(*STATE["verification"]),
|
|
309
|
+
"basis": _ev_nullable(_ev_enum("host_registered_checks", "reported_with_bounded_host_checks", "host_execution_or_missing_report")),
|
|
310
|
+
"checks": _ev_array(_ev_object(_EV_CHECK), 32, lambda row: row["check_ref"]),
|
|
311
|
+
"limitation_refs": _ev_array(_ev_hash, 20), "unmet_requirement_refs": _ev_array(_ev_hash, 20), "error_class": _EV_ERROR}
|
|
312
|
+
_EV_COVERAGE = {"state": _ev_enum("complete", "partial"),
|
|
313
|
+
**{key: _ev_array(_ev_enum(*EVIDENCE_DOMAINS), 8) for key in ("missing", "truncated", "conflicting")},
|
|
314
|
+
"reasons": _ev_array(_ev_enum(*EVIDENCE_REASONS), 10)}
|
|
315
|
+
_EV_BODY = {"app_attempt": _int,
|
|
316
|
+
"generation": _ev_object({**_EV_DOMAIN, **{key: _EV_REF for key in _EV_GENERATION_REFS},
|
|
317
|
+
"provider_evidence": _ev_enum("host_built", "simulated_host", "unknown"),
|
|
318
|
+
"dependency_evidence": _ev_enum("lock_and_installed_metadata", "unknown"), "external_adapter_evidence": _ev_enum("unavailable")}),
|
|
319
|
+
"recipe": _ev_object({**_EV_DOMAIN, **{key: _EV_REF for key in _EV_RECIPE_REFS}, "selection_evidence": _ev_enum("retained_selection", "unknown"),
|
|
320
|
+
"capability_lock": _ev_object({**_EV_DOMAIN, "lock_digest": _EV_REF,
|
|
321
|
+
"entries": _ev_array(_ev_object(_EV_CAPABILITY), 32, lambda row: row["capability_ref"])})}),
|
|
322
|
+
"authority": _ev_object({**_EV_DOMAIN, "owner_ref": _ev_hash, **{key: _EV_REF for key in _EV_AUTHORITY_REFS},
|
|
323
|
+
"basis": _ev_enum("retained_admission", "retained_mapping", "partial_legacy", "unknown")}),
|
|
324
|
+
"artifacts": _ev_array(_ev_object(_EV_ARTIFACT), 64, lambda row: row["artifact_ref"]),
|
|
325
|
+
"outcome": _ev_object(_EV_OUTCOME), "repair": _ev_object(_EV_REPAIR), "coverage": _ev_object(_EV_COVERAGE)}
|
|
326
|
+
_EV_ROOT = {"schema": _ev_enum("egregore.spirit-evidence/v1"),
|
|
327
|
+
**{key: _ev_hash for key in ("owner_ref", "job_ref", "request_ref", "evidence_digest")},
|
|
328
|
+
"revision": _int, "evidence_revision": lambda value: _int(value) and value > 0, "evidence": _ev_object(_EV_BODY)}
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _evidence_hash(value):
|
|
332
|
+
return hashlib.sha256(json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode()).hexdigest()
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def validate_spirit_evidence(value):
|
|
336
|
+
if not _ev_object(_EV_ROOT)(value) or len(json.dumps(value, separators=(",", ":")).encode()) > 64 * 1024:
|
|
337
|
+
raise ValueError("invalid spirit evidence")
|
|
338
|
+
body = value["evidence"]
|
|
339
|
+
covered = {key: body[key] for key in ("generation", "recipe", "authority", "outcome", "repair")}
|
|
340
|
+
covered["capability_lock"] = body["recipe"]["capability_lock"]
|
|
341
|
+
coverage = body["coverage"]
|
|
342
|
+
for name, domain in covered.items():
|
|
343
|
+
if (domain["reason"] is None) != (domain["status"] in {"retained", "not_applicable"}):
|
|
344
|
+
raise ValueError("invalid evidence coverage")
|
|
345
|
+
if domain["status"] in {"missing", "truncated", "conflicting"} and (name not in coverage[domain["status"]] or domain["reason"] not in coverage["reasons"]):
|
|
346
|
+
raise ValueError("invalid evidence coverage")
|
|
347
|
+
if coverage["state"] != ("partial" if any(coverage[key] for key in ("missing", "truncated", "conflicting", "reasons")) else "complete"):
|
|
348
|
+
raise ValueError("invalid evidence coverage")
|
|
349
|
+
for name, required in (("generation", tuple(key for key in _EV_GENERATION_REFS if key != "source_binding_digest")),
|
|
350
|
+
("recipe", _EV_RECIPE_REFS), ("authority", tuple(key for key in _EV_AUTHORITY_REFS if key != "parent_request_ref"))):
|
|
351
|
+
if body[name]["status"] == "retained" and any(body[name][key] is None for key in required):
|
|
352
|
+
raise ValueError("invalid retained provenance")
|
|
353
|
+
if body["authority"]["owner_ref"] != value["owner_ref"]:
|
|
354
|
+
raise ValueError("invalid evidence owner")
|
|
355
|
+
recipe, repair = body["recipe"], body["repair"]
|
|
356
|
+
lock = recipe["capability_lock"]
|
|
357
|
+
if (lock["status"] != "retained" and lock["entries"]
|
|
358
|
+
or lock["status"] == "retained" and (lock["lock_digest"] is None or recipe["status"] != "retained")
|
|
359
|
+
or any(row["recipe_digest"] != recipe["recipe_digest"] or row["generation_digest"] != recipe["generation_digest"] for row in lock["entries"])):
|
|
360
|
+
raise ValueError("invalid capability evidence")
|
|
361
|
+
artifacts = {row["artifact_ref"] for row in body["artifacts"]}
|
|
362
|
+
checks = {row["check_ref"] for row in body["outcome"]["checks"]}
|
|
363
|
+
capabilities = {row["capability_ref"] for row in lock["entries"]}
|
|
364
|
+
for row in body["artifacts"]:
|
|
365
|
+
if (row["request_ref"] != value["request_ref"] or not set(row["check_refs"]) <= checks
|
|
366
|
+
or row["capability_ref"] is not None and (row["capability_ref"] not in capabilities or row["task_ref"] is None)
|
|
367
|
+
or row["retention"] in {"missing", "conflicting"} and "artifacts" not in coverage[row["retention"]]
|
|
368
|
+
or row["external_result"] == "unknown" and row["result_ref"] is not None
|
|
369
|
+
or row["external_result"] == "accepted_preparation" and (row["result_ref"] is None or repair["evidence_scope"] != "accepted_repair_preparation")):
|
|
370
|
+
raise ValueError("invalid artifact attribution")
|
|
371
|
+
if (not set(body["outcome"]["reported_evidence_refs"]) <= artifacts
|
|
372
|
+
or any(not set(row["evidence_refs"]) <= artifacts for row in body["outcome"]["checks"])):
|
|
373
|
+
raise ValueError("invalid outcome attribution")
|
|
374
|
+
if repair["status"] == "retained" and any(repair[key] is None for key in ("task_ref", "repair_attempt", "lease_epoch", "accepted_event_ref", "accepted_result_ref", "accepted_state", "result_state", "accepted_source_ref")):
|
|
375
|
+
raise ValueError("invalid repair acceptance")
|
|
376
|
+
if (repair["evidence_scope"] == "accepted_repair_preparation" and (repair["status"] != "retained" or repair["accepted_state"] != "review_ready"
|
|
377
|
+
or repair["result_state"] != "ready_for_review" or repair["patch_digest"] is None or not repair["verification"]
|
|
378
|
+
or any(row["status"] != "passed" for row in repair["verification"]))
|
|
379
|
+
or any(row["identity_scope"] == "declared_files" and any(row[key] is None for key in ("command_ref", "manifest_digest", "verifier_digest")) for row in repair["verification"])):
|
|
380
|
+
raise ValueError("invalid repair verification")
|
|
381
|
+
if value["evidence_digest"] != _evidence_hash({key: value[key] for key in ("schema", "owner_ref", "job_ref", "revision", "request_ref", "evidence")}):
|
|
382
|
+
raise ValueError("invalid evidence digest")
|
|
383
|
+
return value
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
def spirit_evidence_pair_digest(projection_revision, observation_fingerprint, evidence, history):
|
|
387
|
+
validate_spirit_evidence(evidence)
|
|
388
|
+
if (not _int(projection_revision) or not _ev_hash(observation_fingerprint)
|
|
389
|
+
or not _ev_object({"retained": lambda value: _int(value, 32) and value > 0, "dropped": _int})(history)
|
|
390
|
+
or history["retained"] + history["dropped"] != evidence["evidence_revision"]):
|
|
391
|
+
raise ValueError("invalid evidence pair")
|
|
392
|
+
return _evidence_hash([evidence["owner_ref"], evidence["job_ref"], evidence["revision"], projection_revision,
|
|
393
|
+
observation_fingerprint, evidence["evidence_revision"], evidence["evidence_digest"], history])
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
def validate_spirit_snapshot_v3(snapshot, *, revision):
|
|
397
|
+
_observation_fields(snapshot, {"state", "events", "coverage", "captured_at", *EVIDENCE_EXTRA})
|
|
398
|
+
if len(json.dumps(snapshot, separators=(",", ":")).encode()) > 256 * 1024:
|
|
399
|
+
raise ValueError("evidence snapshot exceeds byte limit")
|
|
400
|
+
validate_spirit_snapshot_v2({key: snapshot[key] for key in ("state", "events", "coverage", "captured_at")}, revision=revision)
|
|
401
|
+
evidence, state = snapshot["evidence"], snapshot["state"]
|
|
402
|
+
paired = spirit_evidence_pair_digest(snapshot["coverage"]["projection_revision"], snapshot["observation_fingerprint"], evidence, snapshot["evidence_history"])
|
|
403
|
+
recorded = evidence["evidence"]
|
|
404
|
+
if (paired != snapshot["pair_digest"] or evidence["revision"] != revision or recorded["app_attempt"] != state["attempt"]
|
|
405
|
+
or any(recorded["outcome"][key] != state[target] for key, target in (("execution", "execution"), ("current_state", "outcome"), ("verification", "verification")))):
|
|
406
|
+
raise ValueError("evidence pair differs from observation")
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
def spirit_snapshot_facts_v3(control_id, revision, snapshot):
|
|
410
|
+
validate_spirit_snapshot_v3(snapshot, revision=revision)
|
|
411
|
+
core = {key: snapshot[key] for key in ("state", "events", "coverage", "captured_at")}
|
|
412
|
+
return {**spirit_snapshot_facts_v2(control_id, revision, core), "schema": "egregore.spirit-session-facts/v3",
|
|
413
|
+
**{key: snapshot[key] for key in EVIDENCE_EXTRA}}
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
|
|
417
|
+
def _marker_key(key):
|
|
418
|
+
return key.strip().strip("\"'").casefold() if isinstance(key, str) else ""
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
def native_marked(meta):
|
|
422
|
+
return isinstance(meta, dict) and any(_marker_key(key) in MARKERS
|
|
423
|
+
and (key != "subject_kind" or value != "human-session") for key, value in meta.items())
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def classify_record(meta, text=""):
|
|
427
|
+
if not isinstance(meta, dict):
|
|
428
|
+
return "unsupported-native"
|
|
429
|
+
# Some generic Markdown readers discard indented header lines. Preserve
|
|
430
|
+
# malformed marker detection without treating that spelling as an identity.
|
|
431
|
+
if isinstance(text, str) and text.startswith("---\n"):
|
|
432
|
+
for line in text[4:].partition("\n---")[0].splitlines():
|
|
433
|
+
key, sep, _ = line.partition(":")
|
|
434
|
+
if sep and _marker_key(key) in MARKERS and key != _marker_key(key):
|
|
435
|
+
return "unsupported-native"
|
|
436
|
+
if not native_marked(meta):
|
|
437
|
+
return "human-session"
|
|
438
|
+
if any(_marker_key(key) in MARKERS and key != _marker_key(key) for key in meta):
|
|
439
|
+
return "unsupported-native"
|
|
440
|
+
if (meta.get("kind") != "session-record" or meta.get("producer") != PRODUCER
|
|
441
|
+
or meta.get("subject_kind") != "durable-intent" or not CONTROL.fullmatch(str(meta.get("session", "")))
|
|
442
|
+
or not PRINCIPAL.fullmatch(str(meta.get("producer_principal_id", "")))
|
|
443
|
+
or meta.get("from") != meta.get("producer_principal_id")
|
|
444
|
+
or not ACTOR.fullmatch(str(meta.get("record_owner_actor_id", "")))
|
|
445
|
+
or meta.get("requester_actor_id") != meta.get("record_owner_actor_id")
|
|
446
|
+
or not isinstance(meta.get("producer_identity"), str) or not 1 <= len(meta["producer_identity"]) <= 160
|
|
447
|
+
or not _int(meta.get("record_revision")) or meta["record_revision"] < 1
|
|
448
|
+
or not isinstance(meta.get("detail"), str) or meta["detail"] not in {"metadata", "stub"}):
|
|
449
|
+
return "unsupported-native"
|
|
450
|
+
if meta["detail"] != "stub" and (not _int(meta.get("source_revision"))
|
|
451
|
+
or not HASH.fullmatch(str(meta.get("source_snapshot_digest", "")))
|
|
452
|
+
or not _int(meta.get("observed_event_count"), 512)
|
|
453
|
+
or meta.get("through") is not None and not _stamp(meta["through"])):
|
|
454
|
+
return "unsupported-native"
|
|
455
|
+
if meta["detail"] != "stub" and any(key in meta for key in ("source_projection_version", "source_projection_revision", "source_evidence_revision")):
|
|
456
|
+
if (type(meta.get("source_projection_version")) is not int or meta["source_projection_version"] not in {2, 3}
|
|
457
|
+
or not _int(meta.get("source_projection_revision")) or not _int(meta["observed_event_count"], 256)):
|
|
458
|
+
return "unsupported-native"
|
|
459
|
+
if meta["source_projection_version"] == 3:
|
|
460
|
+
if not _int(meta.get("source_evidence_revision")) or meta["source_evidence_revision"] < 1:
|
|
461
|
+
return "unsupported-native"
|
|
462
|
+
elif "source_evidence_revision" in meta:
|
|
463
|
+
return "unsupported-native"
|
|
464
|
+
return "durable-intent"
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
def _object(pairs):
|
|
468
|
+
value = {}
|
|
469
|
+
for key, item in pairs:
|
|
470
|
+
if key in value:
|
|
471
|
+
raise ValueError("duplicate metadata field")
|
|
472
|
+
value[key] = item
|
|
473
|
+
return value
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
def decode_spirit_json(raw):
|
|
477
|
+
"""Reject ambiguous keys and non-JSON numbers at private/public boundaries."""
|
|
478
|
+
def invalid_constant(_value):
|
|
479
|
+
raise ValueError("invalid metadata number")
|
|
480
|
+
return json.loads(raw, object_pairs_hook=_object, parse_constant=invalid_constant)
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def _facts(meta, text):
|
|
484
|
+
if not isinstance(text, str) or len(text.encode()) > 256 * 1024:
|
|
485
|
+
return None
|
|
486
|
+
sections = re.findall(r"^## Observed metadata\n\n```json\n(.*?)\n```\s*(?=\Z)", text, re.M | re.S)
|
|
487
|
+
if len(sections) != 1:
|
|
488
|
+
return None
|
|
489
|
+
try:
|
|
490
|
+
value = decode_spirit_json(sections[0])
|
|
491
|
+
if isinstance(value, dict) and value.get("schema") in {"egregore.spirit-session-facts/v2", "egregore.spirit-session-facts/v3"}:
|
|
492
|
+
version = 3 if value["schema"].endswith("/v3") else 2
|
|
493
|
+
if (meta.get("source_projection_version") != version or type(meta.get("source_projection_version")) is not int
|
|
494
|
+
or not _int(meta.get("source_projection_revision"))):
|
|
495
|
+
return None
|
|
496
|
+
snapshot = {key: value[key] for key in ("state", "events", "coverage", "captured_at", *(EVIDENCE_EXTRA if version == 3 else ()))}
|
|
497
|
+
builder = spirit_snapshot_facts_v3 if version == 3 else spirit_snapshot_facts_v2
|
|
498
|
+
expected = builder(meta["session"], meta["source_revision"], snapshot)
|
|
499
|
+
if (version == 3 and (not _int(meta.get("source_evidence_revision"))
|
|
500
|
+
or meta["source_evidence_revision"] != expected["evidence"]["evidence_revision"])
|
|
501
|
+
or version == 2 and "source_evidence_revision" in meta):
|
|
502
|
+
return None
|
|
503
|
+
if (expected["projection_revision"] != meta["source_projection_revision"]
|
|
504
|
+
or expected["observed_event_count"] != meta["observed_event_count"]
|
|
505
|
+
or expected["observed_through"] != meta.get("through")
|
|
506
|
+
or json.dumps(expected, sort_keys=True, separators=(",", ":")) != json.dumps(value, sort_keys=True, separators=(",", ":"))):
|
|
507
|
+
return None
|
|
508
|
+
return expected
|
|
509
|
+
if any(key in meta for key in ("source_projection_version", "source_projection_revision", "source_evidence_revision")):
|
|
510
|
+
return None
|
|
511
|
+
keys = {"schema", "harness", "subject_kind", "control_id", "intents", "current_revision",
|
|
512
|
+
"observed_revision_count", "state", "observed_event_count", "terminal_tool_results", "gap_counts",
|
|
513
|
+
"coverage", "observed_through", "captured_at", "native_event_through", "cost", "duration",
|
|
514
|
+
"generation_count", "compactions", "canonical_transcript", "complete_history"}
|
|
515
|
+
if (not isinstance(value, dict) or set(value) != keys or value["schema"] != "egregore.spirit-session-facts/v1"
|
|
516
|
+
or value["harness"] != "pi-durable" or value["subject_kind"] != "durable-intent"
|
|
517
|
+
or value["control_id"] != meta["session"] or type(value["intents"]) is not int or value["intents"] != 1
|
|
518
|
+
or not _int(value["current_revision"]) or value["current_revision"] != meta["source_revision"]
|
|
519
|
+
or not _int(value["observed_revision_count"], 512) or not _int(value["observed_event_count"], 512)
|
|
520
|
+
or value["observed_event_count"] != meta["observed_event_count"]
|
|
521
|
+
or value["observed_through"] != meta.get("through") or not _stamp(value["captured_at"])
|
|
522
|
+
or any(value[key] is not None for key in ("native_event_through", "cost", "duration", "generation_count", "compactions"))
|
|
523
|
+
or value["canonical_transcript"] is not False or value["complete_history"] is not False):
|
|
524
|
+
return None
|
|
525
|
+
state, coverage, tools, gaps = (value[key] for key in ("state", "coverage", "terminal_tool_results", "gap_counts"))
|
|
526
|
+
if (not isinstance(state, dict) or set(state) != {*STATE, "revision", "attempt"}
|
|
527
|
+
or not _int(state["revision"]) or state["revision"] != meta["source_revision"] or not _int(state["attempt"])
|
|
528
|
+
or any(not isinstance(state[key], str) or state[key] not in allowed for key, allowed in STATE.items())
|
|
529
|
+
or not isinstance(coverage, dict) or set(coverage) != {"historical", "pi_transient", "bounded", "write_failures"}
|
|
530
|
+
or coverage["historical"] != "unknown" or coverage["pi_transient"] != "unknown"
|
|
531
|
+
or type(coverage["bounded"]) is not bool or not _int(coverage["write_failures"])
|
|
532
|
+
or not isinstance(tools, dict) or set(tools) != {"observed", "success", "error", "unknown"}
|
|
533
|
+
or any(not _int(number, 512) for number in tools.values())
|
|
534
|
+
or tools["observed"] != sum(tools[key] for key in ("success", "error", "unknown"))
|
|
535
|
+
or tools["observed"] > value["observed_event_count"]
|
|
536
|
+
or not isinstance(gaps, dict) or not set(gaps) <= GAPS or any(not _int(number, 512) for number in gaps.values())):
|
|
537
|
+
return None
|
|
538
|
+
return value
|
|
539
|
+
except (ValueError, KeyError, TypeError, OverflowError, RecursionError):
|
|
540
|
+
return None
|
|
541
|
+
|
|
542
|
+
|
|
543
|
+
def record_subject(meta, text=""):
|
|
544
|
+
kind = classify_record(meta, text)
|
|
545
|
+
if kind != "durable-intent":
|
|
546
|
+
return {"subject_kind": kind}
|
|
547
|
+
if meta["detail"] == "stub":
|
|
548
|
+
return {"subject_kind": kind, "held": True}
|
|
549
|
+
facts = _facts(meta, text)
|
|
550
|
+
result = {"subject_kind": kind, "held": False, "control_id": meta["session"], "producer": PRODUCER,
|
|
551
|
+
"producer_principal_id": meta["producer_principal_id"], "record_owner_actor_id": meta["record_owner_actor_id"],
|
|
552
|
+
"record_revision": meta["record_revision"], "source_revision": meta["source_revision"], "through": meta.get("through"),
|
|
553
|
+
"state": facts["state"] if facts else None, "coverage": facts["coverage"] if facts else None,
|
|
554
|
+
"observed_event_count": facts["observed_event_count"] if facts else None,
|
|
555
|
+
"metadata_status": "validated" if facts else "unavailable", "cost": None, "duration": None,
|
|
556
|
+
"complete_history": False}
|
|
557
|
+
if facts and facts["schema"] in {"egregore.spirit-session-facts/v2", "egregore.spirit-session-facts/v3"}:
|
|
558
|
+
result.update(projection_version=3 if facts["schema"].endswith("/v3") else 2, projection_revision=facts["projection_revision"],
|
|
559
|
+
observed_event_counts=facts["observed_event_counts"])
|
|
560
|
+
if result["projection_version"] == 3:
|
|
561
|
+
evidence = facts["evidence"]
|
|
562
|
+
result.update(evidence_revision=evidence["evidence_revision"], evidence_coverage=evidence["evidence"]["coverage"],
|
|
563
|
+
evidence_history=facts["evidence_history"])
|
|
564
|
+
return result
|
|
@@ -17,7 +17,7 @@ from .contracts import (
|
|
|
17
17
|
WritebackStatus,
|
|
18
18
|
)
|
|
19
19
|
from .runtime import EgregoreRuntime
|
|
20
|
-
from .policy import
|
|
20
|
+
from .policy import allows, action_for
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
RESEARCH_INGEST_SCHEMA_VERSION = "egregore-research-ingest/v1"
|
|
@@ -164,9 +164,7 @@ class CanonicalResearchIngestService:
|
|
|
164
164
|
raise PermissionError("; ".join(decision.reasons) or "research ingest denied")
|
|
165
165
|
|
|
166
166
|
for _, _, _, _, canonical_path, _ in metadata:
|
|
167
|
-
if decision
|
|
168
|
-
f"memory/{canonical_path}", decision.scopes
|
|
169
|
-
):
|
|
167
|
+
if not allows(decision, f"memory/{canonical_path}", "write", root=self.runtime.canonical_root):
|
|
170
168
|
raise PermissionError(
|
|
171
169
|
"research artifact path is outside the actor's authorized scope"
|
|
172
170
|
)
|