@mnstry/atelier 0.2.0-alpha.11 → 0.2.0-alpha.13
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/CHANGELOG.md +1005 -0
- package/README.md +149 -9
- package/contracts/atelier-adoption-policy.v2.schema.json +55 -0
- package/contracts/atelier-architecture.v1.schema.json +258 -0
- package/contracts/atelier-boundary-delegations.v1.schema.json +161 -0
- package/contracts/atelier-boundary-owner-keys.v1.schema.json +98 -0
- package/contracts/atelier-build.v1.schema.json +1076 -0
- package/contracts/atelier-capability.v1.schema.json +1252 -0
- package/contracts/atelier-coordination.v1.schema.json +1050 -0
- package/contracts/atelier-evidence-navigation.v1.schema.json +603 -0
- package/contracts/atelier-harness.v1.schema.json +234 -0
- package/contracts/atelier-ingestion.v1.schema.json +806 -0
- package/contracts/atelier-inquiry.v1.schema.json +1772 -0
- package/contracts/atelier-instruction-adoption.v1.schema.json +490 -0
- package/contracts/atelier-interaction.v1.schema.json +553 -0
- package/contracts/atelier-judgment.v1.schema.json +690 -0
- package/contracts/atelier-knowledge.v1.schema.json +1512 -0
- package/contracts/atelier-learning.v1.schema.json +1095 -0
- package/contracts/atelier-migration.v3.schema.json +64 -0
- package/contracts/atelier-obsidian-generation-manifest.v2.schema.json +372 -0
- package/contracts/atelier-personal-workspace-manifest.v1.schema.json +87 -0
- package/contracts/atelier-personal-workspace-overlay.v1.schema.json +299 -0
- package/contracts/atelier-template-adoption.v1.schema.json +223 -0
- package/contracts/atelier-template-profile.v1.schema.json +1168 -0
- package/contracts/atelier-trackable.v1.schema.json +781 -0
- package/contracts/atelier-upgrade-plan.v3.schema.json +532 -0
- package/contracts/compat-baseline.json +1 -1
- package/docs/architecture.md +89 -0
- package/docs/assurance-controls.md +4 -3
- package/docs/blocks/capability-stewardship.md +17 -0
- package/docs/blocks/claims.md +16 -3
- package/docs/blocks/inquiry-harnesses.md +19 -0
- package/docs/blocks/learning-harnesses.md +17 -0
- package/docs/capability-stewardship.md +269 -0
- package/docs/coordination.md +51 -0
- package/docs/decision-practices.md +111 -0
- package/docs/decisions.md +29 -0
- package/docs/evidence-navigation.md +237 -0
- package/docs/exact-upgrades.md +3 -2
- package/docs/ingestion.md +181 -0
- package/docs/inquiry-harnesses.md +211 -0
- package/docs/install.md +80 -3
- package/docs/intake-and-guides.md +5 -0
- package/docs/integration-contract-decisions.md +24 -2
- package/docs/interaction-and-reflection.md +72 -0
- package/docs/knowledge-graph.md +20 -0
- package/docs/knowledge-health/api.md +30 -0
- package/docs/knowledge-health/assessment/api.md +96 -0
- package/docs/knowledge-health/assessment/capabilities.md +40 -0
- package/docs/knowledge-health/assessment/evaluation.md +27 -0
- package/docs/knowledge-health/assessment/facilitator.md +30 -0
- package/docs/knowledge-health/assessment/quickstart.md +61 -0
- package/docs/knowledge-health/assessment/troubleshooting.md +23 -0
- package/docs/knowledge-health/devday/facilitator.md +16 -0
- package/docs/knowledge-health/devday/participant-response.md +22 -0
- package/docs/knowledge-health/devday/public-default.md +107 -0
- package/docs/knowledge-health/devday/quickstart.md +18 -0
- package/docs/knowledge-health/devday/support.md +22 -0
- package/docs/knowledge-health/exercise.md +17 -0
- package/docs/knowledge-health/facilitator.md +20 -0
- package/docs/knowledge-health/quickstart.md +94 -0
- package/docs/knowledge-health/readme.md +15 -0
- package/docs/knowledge-health/support.md +34 -0
- package/docs/knowledge-setup.md +248 -0
- package/docs/knowledge-workspace.md +264 -0
- package/docs/learning-harnesses.md +238 -0
- package/docs/learning.md +215 -0
- package/docs/local-services.md +242 -26
- package/docs/obsidian-contract.md +878 -36
- package/docs/obsidian-plugin.md +619 -0
- package/docs/obsidian.md +809 -64
- package/docs/optional-ingestion.md +83 -0
- package/docs/personal-workspace.md +257 -0
- package/docs/practical-judgment.md +92 -0
- package/docs/progressive-ingestion-acceptance.md +89 -0
- package/docs/progressive-ingestion.md +235 -0
- package/docs/project-command-inventory.md +1 -0
- package/docs/release-engineering.md +56 -1
- package/docs/repo-boundary-guard.md +94 -0
- package/docs/research-starter-quickstart.md +66 -0
- package/docs/research-starter.md +114 -0
- package/docs/semantic-ingestion.md +198 -0
- package/docs/semantic-operation.md +317 -0
- package/docs/skill-steward.md +6 -0
- package/docs/template-profile.md +164 -0
- package/docs/template-upgrades.md +114 -0
- package/docs/trackables.md +111 -0
- package/docs/upgrade.md +68 -3
- package/fixtures/atelier-architecture/binding/invalid/document.json +19 -0
- package/fixtures/atelier-architecture/binding/valid/document.json +19 -0
- package/fixtures/atelier-architecture/catalog/invalid/document.json +290 -0
- package/fixtures/atelier-architecture/catalog/valid/document.json +343 -0
- package/fixtures/atelier-build-contract/attempt/invalid/document.json +20 -0
- package/fixtures/atelier-build-contract/attempt/valid/document.json +19 -0
- package/fixtures/atelier-build-contract/candidate/invalid/document.json +24 -0
- package/fixtures/atelier-build-contract/candidate/valid/document.json +23 -0
- package/fixtures/atelier-build-contract/decision/invalid/document.json +31 -0
- package/fixtures/atelier-build-contract/decision/valid/document.json +30 -0
- package/fixtures/atelier-build-contract/delivery/invalid/document.json +18 -0
- package/fixtures/atelier-build-contract/delivery/valid/document.json +17 -0
- package/fixtures/atelier-build-contract/gate/invalid/document.json +22 -0
- package/fixtures/atelier-build-contract/gate/valid/document.json +21 -0
- package/fixtures/atelier-build-contract/ledger/invalid/document.json +270 -0
- package/fixtures/atelier-build-contract/ledger/valid/document.json +269 -0
- package/fixtures/atelier-build-contract/objective/invalid/document.json +73 -0
- package/fixtures/atelier-build-contract/objective/valid/document.json +72 -0
- package/fixtures/atelier-build-contract/progress/invalid/document.json +18 -0
- package/fixtures/atelier-build-contract/progress/valid/document.json +17 -0
- package/fixtures/atelier-capability-contract/adoption/invalid/document.json +71 -0
- package/fixtures/atelier-capability-contract/adoption/valid/document.json +70 -0
- package/fixtures/atelier-capability-contract/event/invalid/document.json +18 -0
- package/fixtures/atelier-capability-contract/event/valid/document.json +17 -0
- package/fixtures/atelier-capability-contract/journal/invalid/document.json +382 -0
- package/fixtures/atelier-capability-contract/journal/valid/document.json +381 -0
- package/fixtures/atelier-capability-contract/notice/invalid/document.json +8 -0
- package/fixtures/atelier-capability-contract/notice/valid/document.json +7 -0
- package/fixtures/atelier-capability-contract/package/invalid/document.json +59 -0
- package/fixtures/atelier-capability-contract/package/valid/document.json +58 -0
- package/fixtures/atelier-capability-contract/plan/invalid/document.json +107 -0
- package/fixtures/atelier-capability-contract/plan/valid/document.json +106 -0
- package/fixtures/atelier-capability-contract/release/invalid/document.json +85 -0
- package/fixtures/atelier-capability-contract/release/valid/document.json +84 -0
- package/fixtures/atelier-capability-contract/state/invalid/document.json +316 -0
- package/fixtures/atelier-capability-contract/state/valid/document.json +315 -0
- package/fixtures/atelier-coordination/dependency/invalid/document.json +37 -0
- package/fixtures/atelier-coordination/dependency/valid/document.json +36 -0
- package/fixtures/atelier-coordination/directive/invalid/document.json +25 -0
- package/fixtures/atelier-coordination/directive/valid/document.json +24 -0
- package/fixtures/atelier-coordination/disposition/invalid/document.json +34 -0
- package/fixtures/atelier-coordination/disposition/valid/document.json +33 -0
- package/fixtures/atelier-coordination/outcome/invalid/document.json +39 -0
- package/fixtures/atelier-coordination/outcome/valid/document.json +38 -0
- package/fixtures/atelier-coordination/recovery/invalid/document.json +34 -0
- package/fixtures/atelier-coordination/recovery/valid/document.json +33 -0
- package/fixtures/atelier-harness-contract/handoff/invalid/document.json +25 -0
- package/fixtures/atelier-harness-contract/handoff/valid/document.json +24 -0
- package/fixtures/atelier-ingestion/invalid/authority.json +19 -0
- package/fixtures/atelier-ingestion/semantic-baseline.json +26 -0
- package/fixtures/atelier-ingestion/semantic-operation.json +75 -0
- package/fixtures/atelier-ingestion/valid/plan-input.json +18 -0
- package/fixtures/atelier-inquiry-contract/assessment/invalid/document.json +41 -0
- package/fixtures/atelier-inquiry-contract/assessment/valid/document.json +40 -0
- package/fixtures/atelier-inquiry-contract/bundle/invalid/document.json +51 -0
- package/fixtures/atelier-inquiry-contract/bundle/valid/document.json +50 -0
- package/fixtures/atelier-inquiry-contract/campaign/invalid/document.json +17 -0
- package/fixtures/atelier-inquiry-contract/campaign/valid/document.json +16 -0
- package/fixtures/atelier-inquiry-contract/decision/invalid/document.json +22 -0
- package/fixtures/atelier-inquiry-contract/decision/valid/document.json +21 -0
- package/fixtures/atelier-inquiry-contract/feedback/invalid/document.json +19 -0
- package/fixtures/atelier-inquiry-contract/feedback/valid/document.json +18 -0
- package/fixtures/atelier-inquiry-contract/handoff/invalid/document.json +92 -0
- package/fixtures/atelier-inquiry-contract/handoff/valid/document.json +91 -0
- package/fixtures/atelier-inquiry-contract/hypothesis/invalid/document.json +20 -0
- package/fixtures/atelier-inquiry-contract/hypothesis/valid/document.json +19 -0
- package/fixtures/atelier-inquiry-contract/ledger/invalid/document.json +680 -0
- package/fixtures/atelier-inquiry-contract/ledger/valid/document.json +679 -0
- package/fixtures/atelier-inquiry-contract/legacy-assessment/invalid/document.json +19 -0
- package/fixtures/atelier-inquiry-contract/legacy-assessment/valid/document.json +18 -0
- package/fixtures/atelier-inquiry-contract/request/invalid/document.json +50 -0
- package/fixtures/atelier-inquiry-contract/request/valid/document.json +49 -0
- package/fixtures/atelier-inquiry-contract/source/invalid/document.json +18 -0
- package/fixtures/atelier-inquiry-contract/source/valid/document.json +17 -0
- package/fixtures/atelier-inquiry-contract/withdrawal/invalid/document.json +16 -0
- package/fixtures/atelier-inquiry-contract/withdrawal/valid/document.json +15 -0
- package/fixtures/atelier-instruction-adoption/context-receipt/invalid/document.json +18 -0
- package/fixtures/atelier-instruction-adoption/context-receipt/valid/document.json +17 -0
- package/fixtures/atelier-instruction-adoption/plan/invalid/document.json +63 -0
- package/fixtures/atelier-instruction-adoption/plan/valid/document.json +62 -0
- package/fixtures/atelier-instruction-adoption/receipt/invalid/document.json +22 -0
- package/fixtures/atelier-instruction-adoption/receipt/valid/document.json +21 -0
- package/fixtures/atelier-instruction-adoption/state/invalid/document.json +30 -0
- package/fixtures/atelier-instruction-adoption/state/valid/document.json +29 -0
- package/fixtures/atelier-interaction/act/invalid/document.json +25 -0
- package/fixtures/atelier-interaction/act/valid/document.json +24 -0
- package/fixtures/atelier-interaction/assessment/invalid/document.json +32 -0
- package/fixtures/atelier-interaction/assessment/valid/document.json +31 -0
- package/fixtures/atelier-interaction/observation/invalid/document.json +22 -0
- package/fixtures/atelier-interaction/observation/valid/document.json +21 -0
- package/fixtures/atelier-interaction/policy/invalid/document.json +20 -0
- package/fixtures/atelier-interaction/policy/valid/document.json +19 -0
- package/fixtures/atelier-judgment/case/invalid/document.json +76 -0
- package/fixtures/atelier-judgment/case/valid/document.json +75 -0
- package/fixtures/atelier-judgment/decision/invalid/document.json +38 -0
- package/fixtures/atelier-judgment/decision/valid/document.json +37 -0
- package/fixtures/atelier-judgment/native-decision.md +3 -0
- package/fixtures/atelier-judgment/practice-reconsideration/definition.json +92 -0
- package/fixtures/atelier-judgment/practice-reconsideration/scenario.json +435 -0
- package/fixtures/atelier-knowledge-contract/activation/invalid/document.json +26 -0
- package/fixtures/atelier-knowledge-contract/activation/valid/document.json +25 -0
- package/fixtures/atelier-knowledge-contract/contribution/invalid/document.json +34 -0
- package/fixtures/atelier-knowledge-contract/contribution/valid/document.json +33 -0
- package/fixtures/atelier-knowledge-contract/domain/invalid/document.json +41 -0
- package/fixtures/atelier-knowledge-contract/domain/valid/document.json +40 -0
- package/fixtures/atelier-knowledge-contract/domain-revision/invalid/document.json +46 -0
- package/fixtures/atelier-knowledge-contract/domain-revision/valid/document.json +45 -0
- package/fixtures/atelier-knowledge-contract/evaluation/invalid/document.json +21 -0
- package/fixtures/atelier-knowledge-contract/evaluation/valid/document.json +20 -0
- package/fixtures/atelier-knowledge-contract/ledger/invalid/document.json +318 -0
- package/fixtures/atelier-knowledge-contract/ledger/valid/document.json +317 -0
- package/fixtures/atelier-knowledge-contract/relation/invalid/document.json +25 -0
- package/fixtures/atelier-knowledge-contract/relation/valid/document.json +24 -0
- package/fixtures/atelier-knowledge-contract/review/invalid/document.json +18 -0
- package/fixtures/atelier-knowledge-contract/review/valid/document.json +17 -0
- package/fixtures/atelier-knowledge-contract/withdrawal/invalid/document.json +16 -0
- package/fixtures/atelier-knowledge-contract/withdrawal/valid/document.json +15 -0
- package/fixtures/atelier-learning/invalid/authority.json +14 -0
- package/fixtures/atelier-learning/valid/capture.json +13 -0
- package/fixtures/atelier-personal-workspace-contract/manifest/invalid/authority-key.json +17 -0
- package/fixtures/atelier-personal-workspace-contract/manifest/invalid/nonempty-ext.json +25 -0
- package/fixtures/atelier-personal-workspace-contract/manifest/invalid/wrong-schema.json +22 -0
- package/fixtures/atelier-personal-workspace-contract/manifest/valid/manifest.json +22 -0
- package/fixtures/atelier-personal-workspace-contract/overlay/invalid/authority-key.json +25 -0
- package/fixtures/atelier-personal-workspace-contract/overlay/invalid/unknown-top-level-field.json +27 -0
- package/fixtures/atelier-personal-workspace-contract/overlay/invalid/wrong-schema.json +24 -0
- package/fixtures/atelier-trackable/definition/invalid/document.json +15 -0
- package/fixtures/atelier-trackable/definition/valid/document.json +14 -0
- package/fixtures/atelier-trackable/request/invalid/document.json +23 -0
- package/fixtures/atelier-trackable/request/valid/document.json +22 -0
- package/fixtures/boundary-delegations/invalid/authority-key.v1.json +30 -0
- package/fixtures/boundary-delegations/invalid/push-operation.v1.json +28 -0
- package/fixtures/boundary-delegations/invalid/unsigned.v1.json +25 -0
- package/fixtures/boundary-delegations/valid/empty.v1.json +4 -0
- package/fixtures/boundary-delegations/valid/signed-operator.v1.json +30 -0
- package/fixtures/boundary-owner-keys/invalid/wrong-algorithm.v1.json +15 -0
- package/fixtures/boundary-owner-keys/invalid/wrong-curve.v1.json +15 -0
- package/fixtures/boundary-owner-keys/valid/one-owner.v1.json +17 -0
- package/fixtures/capability-packages/adoption.example.json +70 -0
- package/fixtures/capability-packages/evidence-review/capability-package.json +58 -0
- package/fixtures/capability-packages/evidence-review/capability-release.json +84 -0
- package/fixtures/capability-packages/evidence-review/evaluation.md +15 -0
- package/fixtures/capability-packages/evidence-review/skills/evidence-review/SKILL.md +22 -0
- package/fixtures/capability-packages/evidence-review/skills/evidence-review/references/ledger.md +7 -0
- package/fixtures/capability-packages/research/capability-package.json +88 -0
- package/fixtures/capability-packages/research/capability-release.json +134 -0
- package/fixtures/capability-packages/research/evaluation.md +15 -0
- package/fixtures/capability-packages/research/skills/research-integrate/SKILL.md +29 -0
- package/fixtures/capability-packages/research/skills/research-integrate/references/integration.md +10 -0
- package/fixtures/capability-packages/research/skills/research-plan/SKILL.md +27 -0
- package/fixtures/capability-packages/research/skills/research-plan/references/inquiries.md +26 -0
- package/fixtures/capability-packages/research/skills/research-synthesize/SKILL.md +23 -0
- package/fixtures/capability-packages/research/skills/research-synthesize/references/synthesis.md +12 -0
- package/fixtures/evidence-navigation/invalid/duplicate-operation.json +16 -0
- package/fixtures/evidence-navigation/invalid/instruction-kind.json +36 -0
- package/fixtures/evidence-navigation/invalid/invalid-digest.json +36 -0
- package/fixtures/evidence-navigation/invalid/invalid-time.json +36 -0
- package/fixtures/evidence-navigation/invalid/missing-attribution.json +35 -0
- package/fixtures/evidence-navigation/invalid/reserved-extension.json +19 -0
- package/fixtures/evidence-navigation/invalid/unattributed-acceptance.json +36 -0
- package/fixtures/evidence-navigation/invalid/uncoerced-version.json +16 -0
- package/fixtures/evidence-navigation/invalid/unknown-currency.json +17 -0
- package/fixtures/evidence-navigation/invalid/unknown-selector-field.json +37 -0
- package/fixtures/evidence-navigation/invalid/unsupported-operation.json +15 -0
- package/fixtures/evidence-navigation/valid/claim.json +36 -0
- package/fixtures/evidence-navigation/valid/host.json +16 -0
- package/fixtures/evidence-navigation/valid/profile.json +16 -0
- package/fixtures/evidence-navigation/valid/snapshot.json +17 -0
- package/fixtures/harness-packages/build-harness/capability-package.json +61 -0
- package/fixtures/harness-packages/build-harness/capability-release.json +82 -0
- package/fixtures/harness-packages/build-harness/evaluation.md +3 -0
- package/fixtures/harness-packages/build-harness/skills/atelier-build-harness/SKILL.md +59 -0
- package/fixtures/harness-packages/knowledge-harness/capability-package.json +61 -0
- package/fixtures/harness-packages/knowledge-harness/capability-release.json +82 -0
- package/fixtures/harness-packages/knowledge-harness/evaluation.md +3 -0
- package/fixtures/harness-packages/knowledge-harness/skills/atelier-knowledge-harness/SKILL.md +60 -0
- package/fixtures/harnesses/learning-cycle.json +780 -0
- package/fixtures/inquiry/workshop.json +675 -0
- package/fixtures/inquiry-packages/discovery-harness/capability-package.json +61 -0
- package/fixtures/inquiry-packages/discovery-harness/capability-release.json +82 -0
- package/fixtures/inquiry-packages/discovery-harness/evaluation.md +3 -0
- package/fixtures/inquiry-packages/discovery-harness/skills/atelier-discovery-harness/SKILL.md +48 -0
- package/fixtures/inquiry-packages/research-harness/capability-package.json +61 -0
- package/fixtures/inquiry-packages/research-harness/capability-release.json +82 -0
- package/fixtures/inquiry-packages/research-harness/evaluation.md +3 -0
- package/fixtures/inquiry-packages/research-harness/skills/atelier-research-harness/SKILL.md +43 -0
- package/fixtures/knowledge-health/calibration-suite.json +26 -0
- package/fixtures/knowledge-health/calibration-trial.json +37 -0
- package/fixtures/knowledge-health/corrected/checklist.md +18 -0
- package/fixtures/knowledge-health/devday/participant-record.json +19 -0
- package/fixtures/knowledge-health/expected-summary.json +27 -0
- package/fixtures/knowledge-health/workshop/README.md +20 -0
- package/fixtures/knowledge-health/workshop/atelier.project.json +7 -0
- package/fixtures/knowledge-health/workshop/knowledge-plan.json +65 -0
- package/fixtures/knowledge-health/workshop/records/checklist.md +18 -0
- package/fixtures/knowledge-health/workshop/records/workshop.md +18 -0
- package/fixtures/knowledge-health/workshop/repo-access.v1.json +5 -0
- package/fixtures/knowledge-health/workspace/atelier.project.json +18 -0
- package/fixtures/knowledge-health/workspace/knowledge-plan.json +74 -0
- package/fixtures/knowledge-health/workspace/records/checklist.md +18 -0
- package/fixtures/knowledge-health/workspace/records/pilot.md +16 -0
- package/fixtures/knowledge-health/workspace/repo-access.v1.json +9 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/case-only-collision.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/flat-note-path.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/hidden-folder.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/identity-in-the-body.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/link-breaking-name.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/missing-identity-region.v2.json +156 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/missing-layout-version.v2.json +159 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/note-named-like-a-folder.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/unknown-region-kind.v2.json +160 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/v1-contract-version.v2.json +161 -0
- package/fixtures/obsidian/contracts/generation-manifest-v2/valid/three-notes.v2.json +160 -0
- package/fixtures/obsidian/edits/workspace.json +1 -1
- package/fixtures/obsidian/materialization/expected-full.json +21 -21
- package/fixtures/obsidian/materialization/expected-full.layout-1.json +99 -0
- package/fixtures/obsidian/materialization/expected-scoped.json +7 -7
- package/fixtures/obsidian/materialization/expected-scoped.layout-1.json +39 -0
- package/fixtures/personal-workspace/overlay.json +24 -0
- package/fixtures/templates/local-library.v1.json +75 -0
- package/fixtures/templates/upgrade-contracts/adoption-policy.v2/invalid.json +16 -0
- package/fixtures/templates/upgrade-contracts/adoption-policy.v2/valid.json +13 -0
- package/fixtures/templates/upgrade-contracts/migration.v3/invalid.json +24 -0
- package/fixtures/templates/upgrade-contracts/migration.v3/valid.json +21 -0
- package/fixtures/templates/upgrade-contracts/template-adoption.v1/invalid.json +60 -0
- package/fixtures/templates/upgrade-contracts/template-adoption.v1/valid.json +57 -0
- package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/invalid.json +85 -0
- package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/valid.json +82 -0
- package/package.json +64 -6
- package/plugins/obsidian/main.js +444 -0
- package/plugins/obsidian/manifest.json +9 -0
- package/plugins/obsidian/styles.css +22 -0
- package/skills/claude/atelier-build-harness/SKILL.md +59 -0
- package/skills/claude/atelier-coordination/SKILL.md +32 -0
- package/skills/claude/atelier-discovery-harness/SKILL.md +48 -0
- package/skills/claude/atelier-guided-upgrade/SKILL.md +29 -0
- package/skills/claude/atelier-interaction/SKILL.md +34 -0
- package/skills/claude/atelier-knowledge-harness/SKILL.md +60 -0
- package/skills/claude/atelier-knowledge-setup/SKILL.md +72 -0
- package/skills/claude/atelier-practical-judgment/SKILL.md +64 -0
- package/skills/claude/atelier-research-harness/SKILL.md +43 -0
- package/skills/claude/atelier-skill-steward/SKILL.md +53 -0
- package/skills/claude/atelier-trackables/SKILL.md +40 -0
- package/skills/codex/atelier-build-harness/SKILL.md +59 -0
- package/skills/codex/atelier-coordination/SKILL.md +32 -0
- package/skills/codex/atelier-discovery-harness/SKILL.md +48 -0
- package/skills/codex/atelier-guided-upgrade/SKILL.md +29 -0
- package/skills/codex/atelier-interaction/SKILL.md +34 -0
- package/skills/codex/atelier-knowledge-harness/SKILL.md +60 -0
- package/skills/codex/atelier-knowledge-setup/SKILL.md +72 -0
- package/skills/codex/atelier-practical-judgment/SKILL.md +64 -0
- package/skills/codex/atelier-research-harness/SKILL.md +43 -0
- package/skills/codex/atelier-skill-steward/SKILL.md +53 -0
- package/skills/codex/atelier-trackables/SKILL.md +40 -0
- package/src/architecture/catalog.mjs +355 -0
- package/src/architecture/index.mjs +93 -0
- package/src/boundary/content-rules.mjs +4 -2
- package/src/boundary/delegation-command.mjs +158 -0
- package/src/boundary/delegation.mjs +396 -0
- package/src/boundary/policy.mjs +52 -10
- package/src/build/git.mjs +18 -0
- package/src/build/index.mjs +3 -0
- package/src/build/ledger.mjs +88 -0
- package/src/capabilities/adoption.mjs +381 -0
- package/src/capabilities/evidence.mjs +140 -0
- package/src/capabilities/files.mjs +124 -0
- package/src/capabilities/index.mjs +4 -0
- package/src/capabilities/instructions.mjs +250 -0
- package/src/capabilities/package.mjs +136 -0
- package/src/cli/command-failure.mjs +36 -0
- package/src/cli/execute-command.mjs +16 -2
- package/src/cli/run.mjs +65 -9
- package/src/coauthor/store.mjs +43 -2
- package/src/collaboration/proposals.mjs +189 -0
- package/src/commands/architecture.mjs +27 -0
- package/src/commands/boundary.mjs +3 -0
- package/src/commands/capability.mjs +73 -0
- package/src/commands/enroll.mjs +3 -0
- package/src/commands/harness.mjs +79 -0
- package/src/commands/ingest.mjs +39 -0
- package/src/commands/init.mjs +4 -1
- package/src/commands/inquiry.mjs +66 -0
- package/src/commands/knowledge-semantic.mjs +128 -0
- package/src/commands/knowledge.mjs +119 -0
- package/src/commands/learn.mjs +61 -0
- package/src/commands/obsidian.mjs +832 -62
- package/src/commands/practice.mjs +24 -0
- package/src/commands/setup.mjs +22 -2
- package/src/commands/trackable.mjs +19 -0
- package/src/contracts/corpus.mjs +36 -0
- package/src/coordination/index.mjs +110 -0
- package/src/egress/forbidden-egress.mjs +2 -0
- package/src/evidence-navigation/contracts.mjs +60 -0
- package/src/evidence-navigation/evaluate.mjs +69 -0
- package/src/evidence-navigation/generation.mjs +22 -0
- package/src/evidence-navigation/index.mjs +2 -0
- package/src/evidence-navigation/local.mjs +167 -0
- package/src/evidence-navigation/schema.generated.mjs +604 -0
- package/src/evidence-navigation/types.mjs +12 -0
- package/src/export/atelier-export-contract.mjs +35 -8
- package/src/graph/enroll.mjs +248 -0
- package/src/graph/graph.mjs +17 -1
- package/src/graph/knowledge-graph.mjs +58 -8
- package/src/harnesses/contracts.mjs +41 -0
- package/src/harnesses/exchange.mjs +154 -0
- package/src/harnesses/feedback.mjs +16 -0
- package/src/harnesses/history.mjs +37 -0
- package/src/harnesses/index.mjs +4 -0
- package/src/harnesses/store.mjs +29 -0
- package/src/ingestion/contracts.mjs +41 -0
- package/src/ingestion/evaluation.mjs +119 -0
- package/src/ingestion/optional/composition/index.mjs +171 -0
- package/src/ingestion/optional/evaluation/index.mjs +219 -0
- package/src/ingestion/optional/graphify/composition-binding.mjs +60 -0
- package/src/ingestion/optional/graphify/composition_receiving.py +185 -0
- package/src/ingestion/optional/graphify/graphify_supplier.py +933 -0
- package/src/ingestion/optional/graphify/json_bridge.py +163 -0
- package/src/ingestion/optional/graphify/query_receiving.py +209 -0
- package/src/ingestion/optional/index.mjs +9 -0
- package/src/ingestion/optional/jev/bridge.mjs +166 -0
- package/src/ingestion/optional/jev/composition-binding.mjs +116 -0
- package/src/ingestion/optional/jev/judgments.mjs +174 -0
- package/src/ingestion/optional/jev/native-translation.mjs +32 -0
- package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/__init__.py +7 -0
- package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/adapter.py +966 -0
- package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/artifacts.py +101 -0
- package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/projection.py +146 -0
- package/src/ingestion/optional/lightrag/composition-binding.mjs +67 -0
- package/src/ingestion/optional/vanilla/composition-binding.mjs +102 -0
- package/src/ingestion/optional/vanilla/index.mjs +218 -0
- package/src/ingestion/optional/vanilla/native-normalization.mjs +187 -0
- package/src/ingestion/processors.mjs +218 -0
- package/src/ingestion/semantic.mjs +203 -0
- package/src/ingestion/store.mjs +372 -0
- package/src/inquiry/beliefs.mjs +32 -0
- package/src/inquiry/contracts.mjs +23 -0
- package/src/inquiry/index.mjs +5 -0
- package/src/inquiry/ledger.mjs +135 -0
- package/src/inquiry/lenses.mjs +15 -0
- package/src/inquiry/projection.mjs +77 -0
- package/src/inquiry/store.mjs +35 -0
- package/src/intake/read-scope.mjs +12 -0
- package/src/intake/store.mjs +69 -6
- package/src/interaction/index.mjs +91 -0
- package/src/judgment/index.mjs +82 -0
- package/src/judgment/practice-evaluation.mjs +70 -0
- package/src/judgment/practice.mjs +144 -0
- package/src/knowledge/context.mjs +217 -0
- package/src/knowledge/index.mjs +5 -0
- package/src/knowledge/ingestion.mjs +79 -0
- package/src/knowledge/intake.mjs +33 -0
- package/src/knowledge/ledger.mjs +110 -0
- package/src/knowledge/participatory/guidance.mjs +36 -0
- package/src/knowledge/participatory/guided-contribution.mjs +44 -0
- package/src/knowledge/participatory/index.mjs +2 -0
- package/src/knowledge/participatory/installed-workshop.mjs +108 -0
- package/src/knowledge/plan.mjs +67 -0
- package/src/knowledge/projection.mjs +43 -0
- package/src/knowledge/semantic-operation.mjs +567 -0
- package/src/knowledge/sessions.mjs +467 -0
- package/src/knowledge/workspace.mjs +295 -0
- package/src/knowledge-health/assessment.mjs +165 -0
- package/src/knowledge-health/capabilities.mjs +350 -0
- package/src/knowledge-health/context.mjs +62 -0
- package/src/knowledge-health/example.mjs +47 -0
- package/src/knowledge-health/index.mjs +10 -0
- package/src/knowledge-health/owner-join.mjs +88 -0
- package/src/knowledge-health/proof/common.mjs +24 -0
- package/src/knowledge-health/proof/installed-check.mjs +100 -0
- package/src/knowledge-health/proof/native-readback.mjs +37 -0
- package/src/knowledge-health/proof/rehearse.mjs +68 -0
- package/src/knowledge-health/proof/validate-participant.mjs +17 -0
- package/src/knowledge-health/proof/verify-native-capture.mjs +14 -0
- package/src/knowledge-health/proof/workshop-check.mjs +36 -0
- package/src/knowledge-health/rule-identity.mjs +44 -0
- package/src/knowledge-health/workshop-composition.mjs +19 -0
- package/src/knowledge-health/workshop-profile.mjs +176 -0
- package/src/knowledge-health/workshop.mjs +99 -0
- package/src/learning/adapters.mjs +68 -0
- package/src/learning/assistance.mjs +127 -0
- package/src/learning/contracts.mjs +65 -0
- package/src/learning/store.mjs +272 -0
- package/src/personal-workspace/index.mjs +391 -0
- package/src/personal-workspace/selection.mjs +385 -0
- package/src/project/config.mjs +8 -0
- package/src/project/durable-state.mjs +3 -1
- package/src/project/file-class.mjs +1 -1
- package/src/project/private-state.mjs +10 -1
- package/src/projection/obsidian/contracts.mjs +224 -18
- package/src/projection/obsidian/edits/apply.mjs +63 -19
- package/src/projection/obsidian/edits/regions.mjs +137 -42
- package/src/projection/obsidian/materialize/index.mjs +6 -3
- package/src/projection/obsidian/materialize/path-registry.mjs +443 -57
- package/src/projection/obsidian/materialize/prepare-view.mjs +382 -131
- package/src/projection/obsidian/materialize/redaction.mjs +327 -0
- package/src/projection/obsidian/materialize/settings.mjs +113 -34
- package/src/projection/obsidian/personal-workspace.mjs +436 -0
- package/src/projection/obsidian/plugin-bridge/bundle.mjs +50 -0
- package/src/projection/obsidian/plugin-bridge/channel.mjs +174 -0
- package/src/projection/obsidian/plugin-bridge/index.mjs +10 -0
- package/src/projection/obsidian/proposals/adapter.mjs +47 -8
- package/src/projection/obsidian/proposals/observation.mjs +20 -7
- package/src/projection/obsidian/publication/bridge-script.mjs +11 -5
- package/src/projection/obsidian/publication/index.mjs +2 -1
- package/src/projection/obsidian/publication/publisher.mjs +312 -36
- package/src/projection/obsidian/publication/transport.mjs +63 -8
- package/src/projection/obsidian/publication/vault-list.mjs +200 -0
- package/src/projection/obsidian/recovery/index.mjs +4 -1
- package/src/projection/obsidian/recovery/restart.mjs +3 -1
- package/src/projection/obsidian/recovery/store.mjs +168 -8
- package/src/projection/obsidian/selection-ui/contribution.mjs +6 -5
- package/src/projection/obsidian/selection-ui/focus.mjs +14 -7
- package/src/projection/obsidian/selection-ui/selection-state.mjs +2 -2
- package/src/projection/obsidian/selection-ui/selection.mjs +8 -3
- package/src/readiness/readiness.mjs +10 -3
- package/src/reflection/index.mjs +56 -0
- package/src/runtime/obsidian/app-capability.mjs +104 -12
- package/src/runtime/obsidian/app-production-seams.mjs +161 -42
- package/src/runtime/obsidian/app-registration.mjs +313 -0
- package/src/runtime/obsidian/app-restart.mjs +154 -0
- package/src/runtime/obsidian/engine.mjs +288 -29
- package/src/runtime/obsidian/extension-points.mjs +1 -1
- package/src/runtime/obsidian/index.mjs +15 -8
- package/src/runtime/obsidian/launch-plan.mjs +91 -0
- package/src/runtime/obsidian/lifecycle.mjs +193 -22
- package/src/runtime/obsidian/login-item.mjs +332 -0
- package/src/runtime/obsidian/machine-settings.mjs +119 -12
- package/src/runtime/obsidian/opening.mjs +369 -63
- package/src/runtime/obsidian/pending-edits.mjs +10 -0
- package/src/runtime/obsidian/pipeline.mjs +71 -3
- package/src/runtime/obsidian/plugin-channel.mjs +289 -0
- package/src/runtime/obsidian/plugin-choice.mjs +182 -0
- package/src/runtime/obsidian/plugin-drift.mjs +124 -0
- package/src/runtime/obsidian/plugin-presence.mjs +61 -0
- package/src/runtime/obsidian/process-identity.mjs +20 -10
- package/src/runtime/obsidian/project-views.mjs +282 -0
- package/src/runtime/obsidian/questions.mjs +48 -0
- package/src/runtime/obsidian/release-watch.mjs +56 -0
- package/src/runtime/obsidian/service-main.mjs +113 -17
- package/src/runtime/obsidian/service-manager-production.mjs +61 -0
- package/src/runtime/obsidian/service-managers.mjs +275 -0
- package/src/runtime/obsidian/service-record.mjs +105 -5
- package/src/runtime/obsidian/service-server.mjs +50 -12
- package/src/runtime/obsidian/service.mjs +134 -7
- package/src/runtime/obsidian/startup-adapters.mjs +59 -21
- package/src/runtime/obsidian/state-store.mjs +13 -1
- package/src/runtime/obsidian/unheld-evidence.mjs +84 -0
- package/src/runtime/obsidian/vault-location.mjs +300 -0
- package/src/runtime/obsidian/view-counts.mjs +34 -0
- package/src/server/local-sidecar.mjs +193 -7
- package/src/server/server.mjs +45 -6
- package/src/template-bindings/project.mjs +92 -0
- package/src/templates/profile.mjs +225 -0
- package/src/trackables/domain.mjs +175 -0
- package/src/trackables/store.mjs +114 -0
- package/src/ui/knowledge-page.mjs +1388 -0
- package/src/upgrade/explanation.mjs +1 -1
- package/src/upgrade/template-participant.mjs +171 -0
- package/src/upgrade/transaction.mjs +72 -25
- package/src/upgrade/upgrade.mjs +2 -1
- package/templates/external-project-workspace/pack-lifecycle.json +6 -1
- package/templates/knowledge-workspace/.gitattributes +2 -0
- package/templates/knowledge-workspace/AGENTS.md +27 -0
- package/templates/knowledge-workspace/README.md +44 -0
- package/templates/knowledge-workspace/atelier.project.json +22 -0
- package/templates/knowledge-workspace/gitignore +6 -0
- package/templates/knowledge-workspace/knowledge-plan.json +111 -0
- package/templates/knowledge-workspace/knowledge-plan.schema.json +281 -0
- package/templates/knowledge-workspace/records/forecast.md +22 -0
- package/templates/knowledge-workspace/records/inspection.md +25 -0
- package/templates/knowledge-workspace/records/telescope.md +24 -0
- package/templates/knowledge-workspace/repo-access.v1.json +9 -0
|
@@ -0,0 +1,933 @@
|
|
|
1
|
+
"""Private Graphify supplier. Native results are derived artifacts, never authority.
|
|
2
|
+
|
|
3
|
+
No package imports, provider calls, server or watcher are started at import time.
|
|
4
|
+
See README.md for the trusted host interfaces and installed qualification boundary.
|
|
5
|
+
"""
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
import copy
|
|
9
|
+
import array
|
|
10
|
+
import base64
|
|
11
|
+
import fcntl
|
|
12
|
+
import hashlib
|
|
13
|
+
import importlib
|
|
14
|
+
import importlib.abc
|
|
15
|
+
import importlib.machinery
|
|
16
|
+
import functools
|
|
17
|
+
import contextlib
|
|
18
|
+
import inspect
|
|
19
|
+
import json
|
|
20
|
+
import math
|
|
21
|
+
import os
|
|
22
|
+
import re
|
|
23
|
+
import sys
|
|
24
|
+
import stat
|
|
25
|
+
import threading
|
|
26
|
+
import time
|
|
27
|
+
import uuid
|
|
28
|
+
from dataclasses import dataclass, fields, is_dataclass
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Any, Callable
|
|
31
|
+
|
|
32
|
+
PINS = json.loads(Path(__file__).with_name("native-pins.json").read_text())
|
|
33
|
+
NATIVE_CLOSURE = json.loads(Path(__file__).with_name("native-closure-pins.json").read_text())
|
|
34
|
+
_IMPORT_LOCK = threading.RLock()
|
|
35
|
+
_ATTESTED_MODULES: dict[str, tuple[Any, str, str]] = {}
|
|
36
|
+
_NO_NATIVE_RESULT = object()
|
|
37
|
+
UNSUPPORTED = {
|
|
38
|
+
"watch": "Requires admitted host-owned long-lived lifecycle and semantic scheduling.",
|
|
39
|
+
"mcp": "Requires admitted server/session lifecycle and project containment.",
|
|
40
|
+
"http-mcp": "Requires admitted server/authentication lifecycle.",
|
|
41
|
+
"global-graph": "Native global state uses the user's home; isolated state binding is unreceived.",
|
|
42
|
+
"acquire-url": "Source acquisition/network custody is separate and unreceived.",
|
|
43
|
+
"workspace-connectors": "Connector permissions and source custody are unreceived.",
|
|
44
|
+
"database-scip": "Native dependency and source custody qualification is unreceived.",
|
|
45
|
+
"assistant-install": "Native assistant installation/hooks are a separate lifecycle.",
|
|
46
|
+
"export-html-wiki-callflow": "Renderer dependencies and destination qualification are unreceived.",
|
|
47
|
+
"pr-impact": "Native PR operations invoke external gh; offline selected impact is exposed.",
|
|
48
|
+
"save-result-querylog": "Native home/work-memory write custody is unreceived; reflect is bounded.",
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class SupplierError(RuntimeError):
|
|
53
|
+
def __init__(self, code: str, message: str, receipt: dict | None = None):
|
|
54
|
+
super().__init__(message)
|
|
55
|
+
self.code, self.receipt = code, receipt
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def require(condition: Any, code: str, message: str) -> None:
|
|
59
|
+
if not condition:
|
|
60
|
+
raise SupplierError(code, message)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def json_safe(value: Any) -> Any:
|
|
64
|
+
"""Retain native scalar/container types explicitly instead of silently flattening."""
|
|
65
|
+
if value is None or isinstance(value, (bool, str, int)):
|
|
66
|
+
return value
|
|
67
|
+
if isinstance(value, float):
|
|
68
|
+
require(math.isfinite(value), "NON_JSON_NATIVE", "Nonfinite native number")
|
|
69
|
+
return value
|
|
70
|
+
if isinstance(value, Path):
|
|
71
|
+
return {"$nativeType": "path", "value": str(value)}
|
|
72
|
+
if isinstance(value, array.array):
|
|
73
|
+
return {"$nativeType": "array", "typecode": value.typecode, "itemsize": value.itemsize,
|
|
74
|
+
"byteorder": sys.byteorder, "bytesBase64": base64.b64encode(value.tobytes()).decode("ascii"),
|
|
75
|
+
"items": [json_safe(v) for v in value]}
|
|
76
|
+
if is_dataclass(value) and not isinstance(value, type):
|
|
77
|
+
return {"$nativeType": "dataclass", "class": f"{type(value).__module__}.{type(value).__qualname__}",
|
|
78
|
+
"fields": {field.name: json_safe(getattr(value, field.name)) for field in fields(value)}}
|
|
79
|
+
if isinstance(value, dict):
|
|
80
|
+
if all(isinstance(k, str) for k in value):
|
|
81
|
+
return {k: json_safe(v) for k, v in value.items()}
|
|
82
|
+
return {"$nativeType": "mapping", "entries": [[json_safe(k), json_safe(v)] for k, v in value.items()]}
|
|
83
|
+
if isinstance(value, (list, tuple, set, frozenset)):
|
|
84
|
+
data = [json_safe(v) for v in value]
|
|
85
|
+
if isinstance(value, (set, frozenset)):
|
|
86
|
+
data.sort(key=lambda x: json.dumps(x, sort_keys=True))
|
|
87
|
+
return data if isinstance(value, list) else {"$nativeType": type(value).__name__, "items": data}
|
|
88
|
+
if hasattr(value, "nodes") and hasattr(value, "edges") and hasattr(value, "is_directed"):
|
|
89
|
+
multi = value.is_multigraph()
|
|
90
|
+
edges = list(value.edges(data=True, keys=True)) if multi else list(value.edges(data=True))
|
|
91
|
+
return {"$nativeType": "graph", "directed": value.is_directed(), "multigraph": multi,
|
|
92
|
+
"attributes": json_safe(value.graph), "nodes": json_safe(list(value.nodes(data=True))),
|
|
93
|
+
"edges": json_safe(edges)}
|
|
94
|
+
raise SupplierError("NON_JSON_NATIVE", f"Unsupported native artifact type: {type(value).__name__}")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def digest(value: Any) -> str:
|
|
98
|
+
return hashlib.sha256(json.dumps(json_safe(value), sort_keys=True, ensure_ascii=False, separators=(",", ":")).encode()).hexdigest()
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class NativeClosureError(SupplierError):
|
|
102
|
+
"""Closure refusal retaining the distinction between preflight and effects."""
|
|
103
|
+
def __init__(self, code: str, message: str, *, invoked=False, returned=False, result=_NO_NATIVE_RESULT):
|
|
104
|
+
super().__init__(code, message)
|
|
105
|
+
self.native_invoked, self.native_returned, self.native_result = invoked, returned, result
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class _PinnedLoader(importlib.abc.Loader):
|
|
109
|
+
def __init__(self, runtime, name, source):
|
|
110
|
+
self.runtime, self.name, self.source = runtime, name, source
|
|
111
|
+
|
|
112
|
+
def create_module(self, spec):
|
|
113
|
+
return None
|
|
114
|
+
|
|
115
|
+
def exec_module(self, module):
|
|
116
|
+
# Execute the verified source bytes, never a possibly unrelated cached pyc.
|
|
117
|
+
raw = self.runtime._source(self.name)
|
|
118
|
+
exec(compile(raw, str(self.source), "exec", dont_inherit=True), module.__dict__)
|
|
119
|
+
self.runtime._source(self.name)
|
|
120
|
+
_ATTESTED_MODULES[self.name] = (module, str(self.runtime.package_root), hashlib.sha256(raw).hexdigest())
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class _PinnedFinder(importlib.abc.MetaPathFinder):
|
|
124
|
+
def __init__(self, runtime):
|
|
125
|
+
self.runtime = runtime
|
|
126
|
+
|
|
127
|
+
def find_spec(self, fullname, path=None, target=None):
|
|
128
|
+
if fullname != "graphify" and not fullname.startswith("graphify."):
|
|
129
|
+
return None
|
|
130
|
+
self.runtime._source(fullname) # Unknown lazy imports refuse before module execution.
|
|
131
|
+
spec = importlib.machinery.PathFinder.find_spec(fullname, path, target)
|
|
132
|
+
source = self.runtime._path(fullname)
|
|
133
|
+
if spec is None or spec.origin is None or Path(spec.origin).resolve() != source:
|
|
134
|
+
raise NativeClosureError("NATIVE_SOURCE_DRIFT", "Graphify import resolved outside its pinned source")
|
|
135
|
+
spec.loader = _PinnedLoader(self.runtime, fullname, source)
|
|
136
|
+
return spec
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
class InstalledGraphifyRuntime:
|
|
140
|
+
"""Lazy direct binding with the fixed admitted public Graphify source closure.
|
|
141
|
+
|
|
142
|
+
The host must qualify dependencies, source/processor permission and finite budgets.
|
|
143
|
+
This class does not install anything or qualify a provider account.
|
|
144
|
+
"""
|
|
145
|
+
kind = "installed-native"
|
|
146
|
+
|
|
147
|
+
def __init__(self, *, package_root: str | Path, semantic_admitted: bool = False):
|
|
148
|
+
# Explicit host-owned installed package directory, never a guessed import.
|
|
149
|
+
self.package_root = Path(package_root).resolve()
|
|
150
|
+
self.semantic_admitted = semantic_admitted
|
|
151
|
+
|
|
152
|
+
def _path(self, name):
|
|
153
|
+
pin = NATIVE_CLOSURE["modules"].get(name)
|
|
154
|
+
if pin is None:
|
|
155
|
+
raise NativeClosureError("NATIVE_IMPORT_UNADMITTED", "Graphify module is outside the admitted source closure")
|
|
156
|
+
path = (self.package_root / pin["relativePath"]).resolve()
|
|
157
|
+
if not path.is_relative_to(self.package_root):
|
|
158
|
+
raise NativeClosureError("NATIVE_SOURCE_DRIFT", "Pinned module escaped the package root")
|
|
159
|
+
return path
|
|
160
|
+
|
|
161
|
+
def _source(self, name):
|
|
162
|
+
try:
|
|
163
|
+
raw = self._path(name).read_bytes()
|
|
164
|
+
except OSError as exc:
|
|
165
|
+
raise NativeClosureError("NATIVE_SOURCE_DRIFT", "Pinned Graphify source is missing or unreadable") from exc
|
|
166
|
+
if hashlib.sha256(raw).hexdigest() != NATIVE_CLOSURE["modules"][name]["sha256"]:
|
|
167
|
+
raise NativeClosureError("NATIVE_SOURCE_DRIFT", "Graphify dependency differs from the admitted source pin")
|
|
168
|
+
return raw
|
|
169
|
+
|
|
170
|
+
def _audit(self):
|
|
171
|
+
# Check the entire admitted closure before every dispatch, not just modules
|
|
172
|
+
# already imported. This detects a changed lazy dependency before effects.
|
|
173
|
+
for name in NATIVE_CLOSURE["modules"]:
|
|
174
|
+
self._source(name)
|
|
175
|
+
for name, native in list(sys.modules.items()):
|
|
176
|
+
if name != "graphify" and not name.startswith("graphify."):
|
|
177
|
+
continue
|
|
178
|
+
source = self._path(name)
|
|
179
|
+
origin = getattr(getattr(native, "__spec__", None), "origin", None)
|
|
180
|
+
filename = getattr(native, "__file__", None)
|
|
181
|
+
if not origin or not filename or Path(origin).resolve() != source or Path(filename).resolve() != source:
|
|
182
|
+
raise NativeClosureError("NATIVE_SOURCE_DRIFT", "Loaded Graphify module differs from the pinned import origin")
|
|
183
|
+
attested = _ATTESTED_MODULES.get(name)
|
|
184
|
+
if attested != (native, str(self.package_root), NATIVE_CLOSURE["modules"][name]["sha256"]):
|
|
185
|
+
raise NativeClosureError("NATIVE_IMPORT_UNATTESTED", "Preloaded Graphify code was not executed through the pinned source loader")
|
|
186
|
+
|
|
187
|
+
@contextlib.contextmanager
|
|
188
|
+
def _imports(self):
|
|
189
|
+
# Host must keep executable interpreter/importer custody. This scope binds
|
|
190
|
+
# Graphify Python source; public third-party wheels remain host-owned.
|
|
191
|
+
with _IMPORT_LOCK:
|
|
192
|
+
finder = _PinnedFinder(self)
|
|
193
|
+
sys.meta_path.insert(0, finder)
|
|
194
|
+
try:
|
|
195
|
+
yield
|
|
196
|
+
finally:
|
|
197
|
+
sys.meta_path.remove(finder)
|
|
198
|
+
|
|
199
|
+
def resolve(self, module: str, function: str) -> Callable:
|
|
200
|
+
candidates = [p for p in PINS["operations"].values() if p["module"] == module]
|
|
201
|
+
require(module.startswith("graphify.") and len(module.split(".")) == 2, "NATIVE_BINDING", "Only pinned Graphify modules")
|
|
202
|
+
source = self.package_root / (module.split(".")[1] + ".py")
|
|
203
|
+
raw = source.read_bytes()
|
|
204
|
+
require(candidates and hashlib.sha256(raw).hexdigest() == candidates[0]["sourceSha256"],
|
|
205
|
+
"NATIVE_SOURCE_DRIFT", "Installed Graphify module differs from inspected pin")
|
|
206
|
+
with self._imports():
|
|
207
|
+
self._audit()
|
|
208
|
+
native = importlib.import_module(module)
|
|
209
|
+
self._audit()
|
|
210
|
+
call = getattr(native, function)
|
|
211
|
+
require(callable(call) and inspect.getsourcefile(call) is not None
|
|
212
|
+
and Path(inspect.getsourcefile(call)).resolve() == source,
|
|
213
|
+
"NATIVE_BINDING", "Pinned native function is not callable from its pinned source")
|
|
214
|
+
|
|
215
|
+
@functools.wraps(call)
|
|
216
|
+
def pinned_call(*args, **kwargs):
|
|
217
|
+
with self._imports():
|
|
218
|
+
self._audit()
|
|
219
|
+
try:
|
|
220
|
+
result = call(*args, **kwargs)
|
|
221
|
+
except BaseException as exc:
|
|
222
|
+
if isinstance(exc, NativeClosureError):
|
|
223
|
+
exc.native_invoked = True
|
|
224
|
+
raise
|
|
225
|
+
try:
|
|
226
|
+
self._audit()
|
|
227
|
+
except NativeClosureError as drift:
|
|
228
|
+
drift.native_invoked = True
|
|
229
|
+
raise drift from exc
|
|
230
|
+
raise
|
|
231
|
+
try:
|
|
232
|
+
self._audit()
|
|
233
|
+
except NativeClosureError as drift:
|
|
234
|
+
drift.native_invoked, drift.native_returned, drift.native_result = True, True, result
|
|
235
|
+
raise
|
|
236
|
+
return result
|
|
237
|
+
return pinned_call
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@dataclass(frozen=True)
|
|
241
|
+
class AcceptedProjection:
|
|
242
|
+
"""Host transport ONLY, returned by an injected canonical owner verifier.
|
|
243
|
+
|
|
244
|
+
Not a new canonical schema. The host verifies current reviews/activation/head,
|
|
245
|
+
source dependencies and IDs using existing Atelier knowledge APIs before return.
|
|
246
|
+
"""
|
|
247
|
+
extractions: list[dict]
|
|
248
|
+
source_ids: list[str]
|
|
249
|
+
record_ids: list[str]
|
|
250
|
+
history_digest: str
|
|
251
|
+
activation_ref: dict
|
|
252
|
+
qualified_records: list[dict]
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class GraphifySupplier:
|
|
256
|
+
"""Serial, generation-bound callable native workflow with immutable captures.
|
|
257
|
+
|
|
258
|
+
Trusted host supplies `source_catalog()` (current, admitted intake sources),
|
|
259
|
+
optional `map_evidence(native_object, source_descriptor)`, optional
|
|
260
|
+
`accepted_owner(reference)` and `usage_meter(operation)` context objects.
|
|
261
|
+
Callers select operations/settings; callers cannot set acceptance or custody.
|
|
262
|
+
"""
|
|
263
|
+
|
|
264
|
+
def __init__(self, *, runtime: Any, source_root: str | Path, source_root_id: str,
|
|
265
|
+
cache_root: str | Path, artifact_root: str | Path,
|
|
266
|
+
source_catalog: Callable[[], dict[str, dict]],
|
|
267
|
+
map_evidence: Callable | None = None, accepted_owner: Callable | None = None,
|
|
268
|
+
usage_meter: Callable | None = None, whole_root_admitted: bool = False,
|
|
269
|
+
query_hydrator: Callable | None = None, query_verifier: Callable | None = None):
|
|
270
|
+
self.runtime = runtime
|
|
271
|
+
self.root, self.cache, self.artifacts = map(lambda p: Path(p).resolve(), (source_root, cache_root, artifact_root))
|
|
272
|
+
require(bool(source_root_id), "ROOT_ID", "Host-owned logical root ID is required")
|
|
273
|
+
require(self.root != self.cache and self.root != self.artifacts and not self.cache.is_relative_to(self.root)
|
|
274
|
+
and not self.artifacts.is_relative_to(self.root), "ROOT_SCOPE", "Cache/artifact roots must be outside source root")
|
|
275
|
+
require(callable(source_catalog), "SOURCE_OWNER", "An admitted source owner reader is required")
|
|
276
|
+
self.root_id, self.catalog = source_root_id, source_catalog
|
|
277
|
+
self.mapper, self.accepted_owner, self.meter = map_evidence, accepted_owner, usage_meter
|
|
278
|
+
self.query_hydrator, self.query_verifier = query_hydrator, query_verifier
|
|
279
|
+
self.whole_root_admitted = whole_root_admitted
|
|
280
|
+
self.cache.mkdir(parents=True, exist_ok=True)
|
|
281
|
+
self.artifacts.mkdir(parents=True, exist_ok=True)
|
|
282
|
+
self.generations: dict[str, dict] = {}
|
|
283
|
+
self.read_graphs: dict[str, dict] = {}
|
|
284
|
+
self.withdrawn: set[str] = set()
|
|
285
|
+
self.enabled, self.epoch = True, 0
|
|
286
|
+
self.lock = threading.Lock()
|
|
287
|
+
|
|
288
|
+
def capabilities(self) -> dict:
|
|
289
|
+
return {"enabled": self.enabled, "epoch": self.epoch, "implemented": copy.deepcopy(PINS["operations"]),
|
|
290
|
+
"unsupported": UNSUPPORTED.copy(), "runtime": getattr(self.runtime, "kind", "host-injected"),
|
|
291
|
+
"qualification": "supplier boundary only; native dependency/provider/host qualification separate"}
|
|
292
|
+
|
|
293
|
+
def receive_query(self, operation_id: str, *, selections: list | None = None) -> dict:
|
|
294
|
+
"""Receive an existing query via trusted canonical-owner callbacks only.
|
|
295
|
+
|
|
296
|
+
This is a private receiving seam, not another native operation/canonical.
|
|
297
|
+
Original query/host receipts remain distinct from supplemental captures.
|
|
298
|
+
"""
|
|
299
|
+
from query_receiving import receive_query
|
|
300
|
+
return receive_query(self, operation_id, selections=selections)
|
|
301
|
+
|
|
302
|
+
def receive_composition(self, received_query: dict, *, receipt_bridge: Callable, verify_receipt_bridge: Callable) -> dict:
|
|
303
|
+
"""Receive the selected host step receipt independently of evidence hits.
|
|
304
|
+
|
|
305
|
+
No SDK operation or transport is added. Trusted existing host callbacks
|
|
306
|
+
retain their own authority and costs, including successful no-hit queries.
|
|
307
|
+
"""
|
|
308
|
+
from composition_receiving import receive_composition
|
|
309
|
+
return receive_composition(self, received_query, receipt_bridge=receipt_bridge,
|
|
310
|
+
verify_receipt_bridge=verify_receipt_bridge)
|
|
311
|
+
|
|
312
|
+
def remove(self) -> None:
|
|
313
|
+
with self.lock:
|
|
314
|
+
self.enabled = False
|
|
315
|
+
self.epoch += 1
|
|
316
|
+
self.read_graphs.clear()
|
|
317
|
+
for item in self.generations.values():
|
|
318
|
+
item["invalidated"] = "tool-removed"
|
|
319
|
+
|
|
320
|
+
def enable(self) -> None:
|
|
321
|
+
with self.lock:
|
|
322
|
+
self.enabled = True
|
|
323
|
+
self.epoch += 1
|
|
324
|
+
self.read_graphs.clear()
|
|
325
|
+
|
|
326
|
+
def withdraw(self, source_ids: list[str]) -> None:
|
|
327
|
+
with self.lock:
|
|
328
|
+
self.withdrawn.update(source_ids)
|
|
329
|
+
for gid, item in self.generations.items():
|
|
330
|
+
if self.withdrawn.intersection(item["dependencies"]):
|
|
331
|
+
item["invalidated"] = "source-withdrawn"
|
|
332
|
+
self.read_graphs.pop(gid, None)
|
|
333
|
+
|
|
334
|
+
def _sources(self, source_ids: list[str]) -> dict[str, dict]:
|
|
335
|
+
require(isinstance(source_ids, list) and len(source_ids) == len(set(source_ids)), "SOURCE_SCOPE", "Unique selected source IDs required")
|
|
336
|
+
current, result = self.catalog(), {}
|
|
337
|
+
for sid in source_ids:
|
|
338
|
+
require(sid in current and sid not in self.withdrawn, "SOURCE_UNAVAILABLE", "Source absent or withdrawn")
|
|
339
|
+
entry = copy.deepcopy(current[sid])
|
|
340
|
+
require(entry.get("sourceId") == sid and entry.get("revision") is not None,
|
|
341
|
+
"SOURCE_BINDING", "Intake source identity and revision required")
|
|
342
|
+
path = (self.root / entry["path"]).resolve()
|
|
343
|
+
require(path.is_relative_to(self.root) and path.is_file(), "SOURCE_SCOPE", "Source path escaped root or is not a file")
|
|
344
|
+
expected = entry.get("sourceDigest")
|
|
345
|
+
require(isinstance(expected, str) and len(expected) == 64 and hashlib.sha256(path.read_bytes()).hexdigest() == expected,
|
|
346
|
+
"SOURCE_STALE", "Selected source bytes differ from intake digest")
|
|
347
|
+
entry["path"] = path.relative_to(self.root).as_posix()
|
|
348
|
+
result[sid] = entry
|
|
349
|
+
return result
|
|
350
|
+
|
|
351
|
+
def _fresh(self, generation_id: str, kind: str | None = None) -> dict:
|
|
352
|
+
try:
|
|
353
|
+
return self._fresh_checked(generation_id, kind)
|
|
354
|
+
except Exception:
|
|
355
|
+
self.read_graphs.pop(generation_id, None)
|
|
356
|
+
raise
|
|
357
|
+
|
|
358
|
+
def _fresh_checked(self, generation_id: str, kind: str | None = None) -> dict:
|
|
359
|
+
item = self._retained(generation_id, kind)
|
|
360
|
+
require(not item.get("invalidated"),
|
|
361
|
+
"GENERATION_STALE", "Generation invalidated or tool removed")
|
|
362
|
+
now = self._sources(list(item["dependencies"]))
|
|
363
|
+
require(now == item["dependencies"], "GENERATION_STALE", "Source binding/revision changed")
|
|
364
|
+
if item.get("accepted_reference") is not None:
|
|
365
|
+
current = self.accepted_owner(copy.deepcopy(item["accepted_reference"]))
|
|
366
|
+
require(isinstance(current, AcceptedProjection) and digest(current.__dict__) == item["accepted_digest"],
|
|
367
|
+
"ACCEPTANCE_STALE", "Canonical accepted projection changed")
|
|
368
|
+
return item
|
|
369
|
+
|
|
370
|
+
def _retained(self, generation_id: str, kind: str | None = None) -> dict:
|
|
371
|
+
"""Historical artifacts may feed diff/update, never current query authority."""
|
|
372
|
+
item = self.generations.get(generation_id)
|
|
373
|
+
require(item is not None, "GENERATION_UNKNOWN", "Unknown supplier generation")
|
|
374
|
+
require(self.enabled and item["epoch"] == self.epoch, "GENERATION_STALE", "Tool lifecycle invalidated generation")
|
|
375
|
+
require(kind is None or item["kind"] == kind, "GENERATION_KIND", "Wrong native generation kind")
|
|
376
|
+
require(item.get("invalidated") not in {"failed", "cancelled"}, "GENERATION_STALE", "Failed/cancelled generation")
|
|
377
|
+
return item
|
|
378
|
+
|
|
379
|
+
def _write(self, operation_dir: Path, name: str, value: Any) -> dict:
|
|
380
|
+
require(not operation_dir.is_symlink() and operation_dir.resolve() == operation_dir
|
|
381
|
+
and operation_dir.parent == self.artifacts and self.artifacts.resolve() == self.artifacts
|
|
382
|
+
and isinstance(name, str) and Path(name).name == name and name not in {"", ".", ".."},
|
|
383
|
+
"CAPTURE_ROOT_ESCAPE", "Capture destination changed or escaped its owned operation root")
|
|
384
|
+
raw = json.dumps(json_safe(value), ensure_ascii=False, sort_keys=True, indent=2).encode()
|
|
385
|
+
path = operation_dir / name
|
|
386
|
+
# No caller-selected file name; operation directory is exclusive and owned.
|
|
387
|
+
directory_fd = os.open(operation_dir, os.O_RDONLY | os.O_DIRECTORY | os.O_NOFOLLOW)
|
|
388
|
+
try:
|
|
389
|
+
file_fd = os.open(name, os.O_WRONLY | os.O_CREAT | os.O_EXCL | os.O_NOFOLLOW, 0o600, dir_fd=directory_fd)
|
|
390
|
+
with os.fdopen(file_fd, "wb") as out:
|
|
391
|
+
out.write(raw)
|
|
392
|
+
out.flush()
|
|
393
|
+
os.fsync(out.fileno())
|
|
394
|
+
finally:
|
|
395
|
+
os.close(directory_fd)
|
|
396
|
+
return {"path": str(path), "sha256": hashlib.sha256(raw).hexdigest(), "bytes": len(raw)}
|
|
397
|
+
|
|
398
|
+
def read_capture(self, operation_id: str) -> dict:
|
|
399
|
+
"""Read a terminal capture after restart; never restore serving authority.
|
|
400
|
+
|
|
401
|
+
Artifact hashes are checked against the retained receipt. The receipt
|
|
402
|
+
itself needs existing intake custody for an independently trusted pin.
|
|
403
|
+
"""
|
|
404
|
+
require(isinstance(operation_id, str) and re.fullmatch(r"[0-9a-f]{32}", operation_id), "CAPTURE_ID", "Owned capture identifier required")
|
|
405
|
+
directory = self.artifacts / operation_id
|
|
406
|
+
require(directory.is_dir() and not directory.is_symlink(), "CAPTURE_SCOPE", "Owned capture missing")
|
|
407
|
+
receipt_path = directory / "receipt.json"
|
|
408
|
+
require(receipt_path.is_file() and not receipt_path.is_symlink(), "CAPTURE_INCOMPLETE", "Terminal capture receipt missing; native outcome is unknown")
|
|
409
|
+
receipt = json.loads(receipt_path.read_text())
|
|
410
|
+
require(receipt.get("operationId") == operation_id and receipt.get("canonicalMutation") is False
|
|
411
|
+
and receipt.get("authority") == "none" and receipt.get("status") in {"complete", "partial", "failed", "cancelled"},
|
|
412
|
+
"CAPTURE_INCOMPLETE", "Invalid terminal capture")
|
|
413
|
+
artifacts = receipt.get("artifacts", []) + [chunk["capture"] for chunk in receipt.get("chunks", [])]
|
|
414
|
+
for artifact in artifacts:
|
|
415
|
+
path = Path(artifact["path"])
|
|
416
|
+
require(path.resolve().is_relative_to(directory) and path.is_file() and not path.is_symlink()
|
|
417
|
+
and hashlib.sha256(path.read_bytes()).hexdigest() == artifact["sha256"],
|
|
418
|
+
"ARTIFACT_DRIFT", "Retained capture differs from receipt")
|
|
419
|
+
return {"receipt": receipt, "servingAuthorityRestored": False,
|
|
420
|
+
"qualification": "local capture readback; independent receipt pin/intake registration required"}
|
|
421
|
+
|
|
422
|
+
def _invoke(self, operation: str, *args: Any, **kwargs: Any) -> Any:
|
|
423
|
+
pin = PINS["operations"][operation]
|
|
424
|
+
call = self.runtime.resolve(pin["module"], pin["function"])
|
|
425
|
+
try:
|
|
426
|
+
inspect.signature(call).bind(*args, **kwargs)
|
|
427
|
+
except (ValueError, TypeError) as exc:
|
|
428
|
+
raise SupplierError("NATIVE_SIGNATURE", "Selected native function signature is incompatible") from exc
|
|
429
|
+
trace = getattr(self, "_native_trace", None)
|
|
430
|
+
if trace is not None:
|
|
431
|
+
trace["invocations"] += 1
|
|
432
|
+
try:
|
|
433
|
+
result = call(*args, **kwargs)
|
|
434
|
+
except NativeClosureError as exc:
|
|
435
|
+
if trace is not None:
|
|
436
|
+
if not exc.native_invoked:
|
|
437
|
+
trace["invocations"] -= 1
|
|
438
|
+
if exc.native_returned:
|
|
439
|
+
trace["returns"] += 1
|
|
440
|
+
raise
|
|
441
|
+
if trace is not None:
|
|
442
|
+
trace["returns"] += 1
|
|
443
|
+
return result
|
|
444
|
+
|
|
445
|
+
def _inventory_partial_artifacts(self, directory: Path, receipt: dict) -> None:
|
|
446
|
+
"""Bounded no-follow inventory; neither custody errors nor bounds hide failure.
|
|
447
|
+
|
|
448
|
+
Unhashed originals remain on disk. This is hash custody, not completion,
|
|
449
|
+
rollback, usage accounting or authority to read beyond the operation root.
|
|
450
|
+
"""
|
|
451
|
+
limits = {"entries": 4096, "files": 256, "bytes": 16 * 1024 * 1024, "depth": 8}
|
|
452
|
+
inventory = {"bounds": limits, "visitedEntries": 0, "hashedFiles": 0, "readBytes": 0,
|
|
453
|
+
"unlisted": [], "truncated": False, "scope": "owned operation root only; originals retained"}
|
|
454
|
+
receipt["partialArtifactInventory"] = inventory
|
|
455
|
+
require(not directory.is_symlink() and directory.resolve() == directory
|
|
456
|
+
and directory.parent == self.artifacts and self.artifacts.resolve() == self.artifacts,
|
|
457
|
+
"CAPTURE_ROOT_ESCAPE", "Partial capture root changed or escaped")
|
|
458
|
+
known = {a["path"] for a in receipt["artifacts"]}
|
|
459
|
+
flags = os.O_RDONLY | os.O_NOFOLLOW
|
|
460
|
+
def omit(relative, reason):
|
|
461
|
+
inventory["unlisted"].append({"path": relative.as_posix(), "reason": reason})
|
|
462
|
+
def walk(fd, relative, depth):
|
|
463
|
+
entries = []
|
|
464
|
+
with os.scandir(fd) as scan:
|
|
465
|
+
for entry in scan:
|
|
466
|
+
if inventory["visitedEntries"] >= limits["entries"]:
|
|
467
|
+
inventory["truncated"] = True
|
|
468
|
+
omit(relative, "entry-bound")
|
|
469
|
+
break
|
|
470
|
+
inventory["visitedEntries"] += 1
|
|
471
|
+
entries.append(entry)
|
|
472
|
+
for entry in sorted(entries, key=lambda value: value.name):
|
|
473
|
+
child = relative / entry.name
|
|
474
|
+
path = directory / child
|
|
475
|
+
metadata = entry.stat(follow_symlinks=False)
|
|
476
|
+
if stat.S_ISLNK(metadata.st_mode):
|
|
477
|
+
omit(child, "symlink-not-followed")
|
|
478
|
+
continue
|
|
479
|
+
if not path.resolve().is_relative_to(directory):
|
|
480
|
+
omit(child, "root-escape-refused")
|
|
481
|
+
continue
|
|
482
|
+
if stat.S_ISDIR(metadata.st_mode):
|
|
483
|
+
if depth >= limits["depth"]:
|
|
484
|
+
inventory["truncated"] = True; omit(child, "depth-bound")
|
|
485
|
+
continue
|
|
486
|
+
child_fd = os.open(entry.name, flags | os.O_DIRECTORY, dir_fd=fd)
|
|
487
|
+
try:
|
|
488
|
+
walk(child_fd, child, depth + 1)
|
|
489
|
+
finally:
|
|
490
|
+
os.close(child_fd)
|
|
491
|
+
continue
|
|
492
|
+
if not stat.S_ISREG(metadata.st_mode):
|
|
493
|
+
omit(child, "nonregular-file-not-read")
|
|
494
|
+
continue
|
|
495
|
+
if str(path) in known:
|
|
496
|
+
continue
|
|
497
|
+
if inventory["hashedFiles"] >= limits["files"] or metadata.st_size > limits["bytes"] - inventory["readBytes"]:
|
|
498
|
+
inventory["truncated"] = True; omit(child, "file-or-byte-bound")
|
|
499
|
+
continue
|
|
500
|
+
file_fd = os.open(entry.name, flags | os.O_NONBLOCK, dir_fd=fd)
|
|
501
|
+
try:
|
|
502
|
+
before = os.fstat(file_fd)
|
|
503
|
+
require(stat.S_ISREG(before.st_mode) and (before.st_dev, before.st_ino) == (metadata.st_dev, metadata.st_ino)
|
|
504
|
+
and path.resolve().is_relative_to(directory), "CAPTURE_ROOT_ESCAPE", "Partial file changed identity or escaped")
|
|
505
|
+
hasher, size = hashlib.sha256(), 0
|
|
506
|
+
while inventory["readBytes"] < limits["bytes"]:
|
|
507
|
+
chunk = os.read(file_fd, min(65536, limits["bytes"] - inventory["readBytes"]))
|
|
508
|
+
if not chunk:
|
|
509
|
+
break
|
|
510
|
+
inventory["readBytes"] += len(chunk); size += len(chunk); hasher.update(chunk)
|
|
511
|
+
after = os.fstat(file_fd)
|
|
512
|
+
if (before.st_size, before.st_mtime_ns, before.st_ctime_ns) != (after.st_size, after.st_mtime_ns, after.st_ctime_ns) or size != after.st_size:
|
|
513
|
+
inventory["truncated"] = True; omit(child, "changed-or-byte-bound-during-read")
|
|
514
|
+
continue
|
|
515
|
+
receipt["artifacts"].append({"path": str(path), "sha256": hasher.hexdigest(), "bytes": size})
|
|
516
|
+
inventory["hashedFiles"] += 1
|
|
517
|
+
finally:
|
|
518
|
+
os.close(file_fd)
|
|
519
|
+
root_fd = os.open(directory, flags | os.O_DIRECTORY)
|
|
520
|
+
try:
|
|
521
|
+
walk(root_fd, Path(), 0)
|
|
522
|
+
finally:
|
|
523
|
+
os.close(root_fd)
|
|
524
|
+
|
|
525
|
+
def _read_graph_native(self, operation: str, graph: Any, directory: Path, receipt: dict, *args: Any, **kwargs: Any) -> Any:
|
|
526
|
+
# serve's lookup/query paths add array/set caches to graph attributes.
|
|
527
|
+
# Retain those native effects separately; they must not poison the
|
|
528
|
+
# accepted/exploratory graph or its original native JSON exporter.
|
|
529
|
+
gid = receipt["graphGeneration"]
|
|
530
|
+
key = digest({"generation": gid, "operation": operation, "settings": receipt["settings"]})
|
|
531
|
+
cached = self.read_graphs.get(gid)
|
|
532
|
+
reused = cached is not None and cached["key"] == key
|
|
533
|
+
working = cached["graph"] if reused else copy.deepcopy(graph)
|
|
534
|
+
receipt["nativeGraphUse"] = {"isolatedFromGeneration": True, "warmLookupCacheReused": reused,
|
|
535
|
+
"generationId": gid, "settingsKey": key, "maximumCachedGenerations": 8}
|
|
536
|
+
receipt["limitations"].append("Native lookup state is a separate current-generation/settings-bound in-memory graph cache; reuse is not speed or quality proof.")
|
|
537
|
+
try:
|
|
538
|
+
result = self._invoke(operation, working, *args, **kwargs)
|
|
539
|
+
self.read_graphs.pop(gid, None)
|
|
540
|
+
self.read_graphs[gid] = {"key": key, "graph": working}
|
|
541
|
+
while len(self.read_graphs) > 8:
|
|
542
|
+
self.read_graphs.pop(next(iter(self.read_graphs)))
|
|
543
|
+
return result
|
|
544
|
+
except BaseException:
|
|
545
|
+
self.read_graphs.pop(gid, None)
|
|
546
|
+
raise
|
|
547
|
+
finally:
|
|
548
|
+
receipt["artifacts"].append(self._write(directory, "native-read-graph.json", working))
|
|
549
|
+
|
|
550
|
+
def _settings(self, params: dict, allowed: set[str]) -> dict:
|
|
551
|
+
value = copy.deepcopy(params.get("settings", {}))
|
|
552
|
+
require(isinstance(value, dict) and not (set(value) - allowed), "SETTING_UNSUPPORTED", "Unknown or reserved native settings")
|
|
553
|
+
require("api_key" not in value, "SECRET_SETTING", "Credentials belong to the admitted runtime, not receipts")
|
|
554
|
+
if value.get("dedup_llm_backend") is not None:
|
|
555
|
+
require(getattr(self.runtime, "semantic_admitted", False), "PROCESSOR_UNADMITTED", "Model-backed dedup requires host admission")
|
|
556
|
+
return value
|
|
557
|
+
|
|
558
|
+
def call(self, operation: str, params: dict | None = None, *, cancel: threading.Event | None = None) -> dict:
|
|
559
|
+
params = copy.deepcopy(params or {})
|
|
560
|
+
require(operation in PINS["operations"], "CAPABILITY_UNSUPPORTED", UNSUPPORTED.get(operation, "Unknown operation"))
|
|
561
|
+
require(self.lock.acquire(blocking=False), "WRITER_BUSY", "A supplier operation owns the native writer")
|
|
562
|
+
file_lock = None
|
|
563
|
+
receipt, opdir, meter = None, None, None
|
|
564
|
+
failed = False
|
|
565
|
+
self._native_trace = {"invocations": 0, "returns": 0}
|
|
566
|
+
try:
|
|
567
|
+
require(self.enabled, "TOOL_REMOVED", "Graphify is unavailable")
|
|
568
|
+
require(not cancel or not cancel.is_set(), "CANCELLED", "Cancelled before native dispatch")
|
|
569
|
+
file_lock = (self.artifacts / ".writer.lock").open("a+b")
|
|
570
|
+
try:
|
|
571
|
+
fcntl.flock(file_lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
|
572
|
+
except BlockingIOError as exc:
|
|
573
|
+
raise SupplierError("WRITER_BUSY", "Another process owns this supplier destination") from exc
|
|
574
|
+
opid = uuid.uuid4().hex
|
|
575
|
+
opdir = self.artifacts / opid
|
|
576
|
+
opdir.mkdir()
|
|
577
|
+
receipt = {"operationId": opid, "operation": operation, "producer": PINS["upstreamCommit"],
|
|
578
|
+
"runtime": getattr(self.runtime, "kind", "host-injected"), "rootIdentity": self.root_id,
|
|
579
|
+
"root": str(self.root), "cacheRoot": str(self.cache), "epoch": self.epoch,
|
|
580
|
+
"canonicalMutation": False, "authority": "none", "status": "running", "artifacts": [],
|
|
581
|
+
"nativePin": PINS["operations"][operation], "settings": {}, "limitations": []}
|
|
582
|
+
meter = self.meter(operation) if self.meter else None
|
|
583
|
+
if meter:
|
|
584
|
+
meter.start()
|
|
585
|
+
began = time.monotonic()
|
|
586
|
+
result = self._execute(operation, params, opdir, receipt, cancel)
|
|
587
|
+
receipt["elapsedSeconds"] = time.monotonic() - began
|
|
588
|
+
# Preserve completed native output even if final freshness/cancellation
|
|
589
|
+
# checks refuse its use. Recovery must never imply rerunning a processor.
|
|
590
|
+
receipt["artifacts"].append(self._write(opdir, "result.json", result))
|
|
591
|
+
require(not cancel or not cancel.is_set(), "CANCELLED_AFTER_NATIVE", "Native completed after cancellation; output retained, generation unusable")
|
|
592
|
+
if receipt.get("sourceBindings"):
|
|
593
|
+
current = self._sources(list(receipt["sourceBindings"]))
|
|
594
|
+
require(current == receipt["sourceBindings"], "SOURCE_CHANGED_DURING_NATIVE", "Source changed during native operation; output retained as stale")
|
|
595
|
+
if receipt.get("graphGeneration"):
|
|
596
|
+
self._fresh(receipt["graphGeneration"], "graph")
|
|
597
|
+
if receipt.get("generationId"):
|
|
598
|
+
self._fresh(receipt["generationId"])
|
|
599
|
+
receipt["result"] = json_safe(result)
|
|
600
|
+
receipt["status"] = receipt.get("outcome", "complete")
|
|
601
|
+
if operation == "extract-semantic":
|
|
602
|
+
receipt["nativeUsage"] = {k: result.get(k) for k in ("input_tokens", "output_tokens", "model")}
|
|
603
|
+
receipt["limitations"].append("Native aggregate token counters can omit truncated parent/hollow retry work; callbacks are not per-attempt metering.")
|
|
604
|
+
return receipt
|
|
605
|
+
except BaseException as exc:
|
|
606
|
+
failed = True
|
|
607
|
+
if receipt is not None:
|
|
608
|
+
receipt["status"] = "cancelled" if isinstance(exc, SupplierError) and exc.code.startswith("CANCELLED") else "failed"
|
|
609
|
+
receipt["error"] = {"type": type(exc).__name__, "code": getattr(exc, "code", "NATIVE_FAILURE")}
|
|
610
|
+
if isinstance(exc, NativeClosureError):
|
|
611
|
+
receipt["nativeExecution"] = {"invoked": exc.native_invoked, "returned": exc.native_returned,
|
|
612
|
+
"state": "unknown-after-closure-drift" if exc.native_invoked else "native-operation-not-started"}
|
|
613
|
+
receipt["limitations"].append("Closure drift refuses serving; reconcile retained outputs before any replay. No native effect rollback is claimed.")
|
|
614
|
+
if exc.native_result is not _NO_NATIVE_RESULT:
|
|
615
|
+
try:
|
|
616
|
+
receipt["artifacts"].append(self._write(opdir, "post-drift-native-result.json", exc.native_result))
|
|
617
|
+
receipt["partialNativeResult"] = json_safe(exc.native_result)
|
|
618
|
+
except Exception as capture_error:
|
|
619
|
+
receipt["partialCaptureError"] = {"type": type(capture_error).__name__, "code": getattr(capture_error, "code", "CAPTURE_FAILURE")}
|
|
620
|
+
else:
|
|
621
|
+
invoked = self._native_trace["invocations"] > 0
|
|
622
|
+
receipt["nativeExecution"] = {"invoked": invoked,
|
|
623
|
+
"returned": invoked and self._native_trace["returns"] == self._native_trace["invocations"],
|
|
624
|
+
"state": "unknown-after-native-dispatch" if invoked else "native-operation-not-started"}
|
|
625
|
+
# Ordinary exceptions can leave exporter/processor files too.
|
|
626
|
+
# Inventory failures annotate rather than replace the original error.
|
|
627
|
+
try:
|
|
628
|
+
self._inventory_partial_artifacts(opdir, receipt)
|
|
629
|
+
except BaseException as inventory_error:
|
|
630
|
+
receipt["partialArtifactInventoryError"] = {"type": type(inventory_error).__name__, "code": getattr(inventory_error, "code", "CAPTURE_INVENTORY_FAILURE")}
|
|
631
|
+
self.read_graphs.pop(receipt.get("graphGeneration"), None)
|
|
632
|
+
# Do not copy potentially sensitive native error text into portable receipts.
|
|
633
|
+
for item in self.generations.values():
|
|
634
|
+
if item["operationId"] == receipt["operationId"]:
|
|
635
|
+
item["invalidated"] = receipt["status"]
|
|
636
|
+
if isinstance(exc, SupplierError):
|
|
637
|
+
exc.receipt = receipt
|
|
638
|
+
raise
|
|
639
|
+
raise SupplierError("NATIVE_FAILURE", "Native operation failed; captures retained", receipt) from exc
|
|
640
|
+
finally:
|
|
641
|
+
try:
|
|
642
|
+
if receipt is not None:
|
|
643
|
+
if meter:
|
|
644
|
+
try:
|
|
645
|
+
receipt["usage"] = json_safe(meter.finish())
|
|
646
|
+
receipt["usageAssurance"] = "host-metered; host must declare retry coverage"
|
|
647
|
+
except Exception:
|
|
648
|
+
receipt["usageAssurance"] = "unknown; meter failed"
|
|
649
|
+
receipt["limitations"].append("Usage meter failed; do not claim complete cost.")
|
|
650
|
+
else:
|
|
651
|
+
receipt["usage"] = None
|
|
652
|
+
receipt["usageAssurance"] = "unknown; native returned counters only"
|
|
653
|
+
try:
|
|
654
|
+
self._write(opdir, "receipt.json", receipt)
|
|
655
|
+
except BaseException as receipt_error:
|
|
656
|
+
receipt["receiptCaptureError"] = {"type": type(receipt_error).__name__, "code": getattr(receipt_error, "code", "RECEIPT_CAPTURE_FAILURE")}
|
|
657
|
+
if not failed:
|
|
658
|
+
raise
|
|
659
|
+
finally:
|
|
660
|
+
self._native_trace = None
|
|
661
|
+
if file_lock:
|
|
662
|
+
file_lock.close()
|
|
663
|
+
self.lock.release()
|
|
664
|
+
|
|
665
|
+
def _generation(self, op: str, value: Any, sources: dict, receipt: dict, **extras: Any) -> str:
|
|
666
|
+
gid = receipt["operationId"]
|
|
667
|
+
self.generations[gid] = {"kind": op, "value": value, "dependencies": copy.deepcopy(sources),
|
|
668
|
+
"epoch": self.epoch, "operationId": gid, **extras}
|
|
669
|
+
receipt["generationId"] = gid
|
|
670
|
+
receipt["sourceBindings"] = sources
|
|
671
|
+
return gid
|
|
672
|
+
|
|
673
|
+
def _mapping(self, value: dict, sources: dict) -> dict:
|
|
674
|
+
# File association is not passage support. Only host evidence readback
|
|
675
|
+
# can hydrate a canonical evidence receipt; absent locators remain unknown.
|
|
676
|
+
sf = value.get("source_file")
|
|
677
|
+
descriptor = None
|
|
678
|
+
if isinstance(sf, str):
|
|
679
|
+
candidate = (self.root / sf).resolve()
|
|
680
|
+
descriptor = next((s for s in sources.values() if (self.root / s["path"]).resolve() == candidate), None)
|
|
681
|
+
evidence = self.mapper(copy.deepcopy(value), copy.deepcopy(descriptor)) if descriptor and self.mapper else None
|
|
682
|
+
if evidence is not None:
|
|
683
|
+
require(isinstance(evidence, dict) and evidence.get("schema") == "mnstry.atelier-ingestion-evidence@v1"
|
|
684
|
+
and evidence.get("sourceId") == descriptor["sourceId"] and evidence.get("sourceDigest") == descriptor["sourceDigest"]
|
|
685
|
+
and evidence.get("freshness") == "current" and evidence.get("integrity") == "verified"
|
|
686
|
+
and evidence.get("semanticAcceptance") == "pending" and evidence.get("synthesized") is False
|
|
687
|
+
and isinstance(evidence.get("locator"), dict) and isinstance(evidence.get("text"), str)
|
|
688
|
+
and all(evidence.get(key) == descriptor[key] for key in ("attemptId", "planId", "planDigest") if key in descriptor),
|
|
689
|
+
"EVIDENCE_UNVERIFIED", "Canonical evidence readback does not match current source")
|
|
690
|
+
return {"nativeId": value.get("id"), "source": descriptor, "evidence": json_safe(evidence),
|
|
691
|
+
"support": "host-evidence-readback" if evidence is not None else "unknown",
|
|
692
|
+
"identityAcceptance": "pending"}
|
|
693
|
+
|
|
694
|
+
def _execute(self, op: str, p: dict, directory: Path, receipt: dict, cancel: Any) -> Any:
|
|
695
|
+
if op in {"discover", "discover-incremental"}:
|
|
696
|
+
require(self.whole_root_admitted, "ROOT_UNADMITTED", "Native discovery reads the whole root; host admission required")
|
|
697
|
+
settings = self._settings(p, {"follow_symlinks", "extra_excludes", "gitignore", "kind"} if op.endswith("incremental") else {"follow_symlinks", "extra_excludes", "gitignore"})
|
|
698
|
+
receipt["settings"] = settings
|
|
699
|
+
kwargs = {"cache_root": self.cache, "google_workspace": False, **settings}
|
|
700
|
+
if op.endswith("incremental"):
|
|
701
|
+
kwargs["manifest_path"] = str(self.cache / "manifest.json")
|
|
702
|
+
result = self._invoke(op, self.root, **kwargs)
|
|
703
|
+
receipt["limitations"].append("Discovery is inventory, not source admission. Workspace conversion is disabled.")
|
|
704
|
+
return result
|
|
705
|
+
if op in {"extract-structure", "extract-semantic", "cache-check"}:
|
|
706
|
+
sources = self._sources(p.get("source_ids", []))
|
|
707
|
+
require(sources, "SOURCE_SCOPE", "Selected source bindings required")
|
|
708
|
+
paths = [self.root / s["path"] for s in sources.values()]
|
|
709
|
+
allowed = {"parallel", "max_workers", "resolution_context_nodes", "resolution_context_edges"}
|
|
710
|
+
if op == "extract-semantic":
|
|
711
|
+
require(getattr(self.runtime, "semantic_admitted", False), "PROCESSOR_UNADMITTED", "Semantic/media processor runtime must be explicitly admitted")
|
|
712
|
+
allowed = {"backend", "model", "chunk_size", "token_budget", "max_concurrency", "max_retry_depth", "deep_mode"}
|
|
713
|
+
elif op == "cache-check":
|
|
714
|
+
allowed = {"mode", "prompt", "prompt_file"}
|
|
715
|
+
settings = self._settings(p, allowed)
|
|
716
|
+
if op == "extract-structure":
|
|
717
|
+
settings = {"parallel": False, "max_workers": 1, **settings}
|
|
718
|
+
require(type(settings["parallel"]) is bool and type(settings["max_workers"]) is int
|
|
719
|
+
and 1 <= settings["max_workers"] <= 32, "BUDGET_UNBOUNDED", "Finite native structural worker bound required")
|
|
720
|
+
if op == "cache-check":
|
|
721
|
+
require(settings.get("prompt") is None or isinstance(settings["prompt"], str), "PROMPT_SCOPE", "Prompt must be explicit text")
|
|
722
|
+
if settings.get("prompt_file") is not None:
|
|
723
|
+
prompt_file = Path(settings["prompt_file"]).resolve()
|
|
724
|
+
require(prompt_file.is_relative_to(self.artifacts) and prompt_file.is_file(),
|
|
725
|
+
"PROMPT_SCOPE", "Prompt file must be an owned supplier artifact")
|
|
726
|
+
settings["prompt_file"] = prompt_file
|
|
727
|
+
if op == "extract-semantic":
|
|
728
|
+
settings = {"chunk_size": 20, "token_budget": 60000, "max_concurrency": 1, "max_retry_depth": 0, **settings}
|
|
729
|
+
require(bool(settings.get("backend")) and bool(settings.get("model")), "MODEL_PIN", "Explicit backend/model required")
|
|
730
|
+
for key, maximum in (("chunk_size", 100), ("token_budget", 1000000), ("max_concurrency", 32), ("max_retry_depth", 8)):
|
|
731
|
+
minimum = 0 if key == "max_retry_depth" else 1
|
|
732
|
+
require(type(settings[key]) is int and minimum <= settings[key] <= maximum, "BUDGET_UNBOUNDED", "Finite native execution bounds required")
|
|
733
|
+
chunks, chunk_lock = [], threading.Lock()
|
|
734
|
+
|
|
735
|
+
def done(index: int, total: int, raw: dict) -> None:
|
|
736
|
+
# Callback runs on multiple native worker threads. Capture before
|
|
737
|
+
# cancellation; it contains top-level post-retry output only.
|
|
738
|
+
with chunk_lock:
|
|
739
|
+
capture = self._write(directory, f"chunk-{len(chunks):06d}.json", {"index": index, "total": total, "raw": copy.deepcopy(raw)})
|
|
740
|
+
chunks.append({"index": index, "total": total, "capture": capture})
|
|
741
|
+
receipt["chunks"] = copy.deepcopy(chunks)
|
|
742
|
+
require(not cancel or not cancel.is_set(), "CANCELLED", "Cancelled at chunk boundary")
|
|
743
|
+
|
|
744
|
+
settings["on_chunk_done"] = done
|
|
745
|
+
receipt["settings"] = {k: json_safe(v) for k, v in settings.items() if k != "on_chunk_done"}
|
|
746
|
+
result = self._invoke(op, paths if op != "cache-check" else [str(x) for x in paths], root=self.root, cache_root=self.cache, **settings)
|
|
747
|
+
if op == "cache-check":
|
|
748
|
+
return result
|
|
749
|
+
receipt["artifacts"].append(self._write(directory, "raw-prebuild.json", result))
|
|
750
|
+
require(isinstance(result, dict), "NATIVE_OUTPUT", "Native extraction must return a dictionary")
|
|
751
|
+
receipt["portableMappings"] = {name: [self._mapping(x, sources) for x in result.get(name, [])]
|
|
752
|
+
for name in ("nodes", "edges", "hyperedges")}
|
|
753
|
+
incomplete = bool(result.get("failed_chunks") or result.get("failed_sources") or result.get("uncovered_files")
|
|
754
|
+
or result.get("_partial_files") or result.get("_partial"))
|
|
755
|
+
receipt["outcome"] = "partial" if incomplete else "complete"
|
|
756
|
+
receipt["coverage"] = {k: copy.deepcopy(result.get(k)) for k in ("failed_chunks", "failed_sources", "extracted_sources", "uncovered_files", "_partial_files", "_partial", "out_of_scope_dropped")}
|
|
757
|
+
receipt["coverage"]["assurance"] = "native-reported; no semantic-completeness claim"
|
|
758
|
+
self._generation("extraction", copy.deepcopy(result), sources, receipt)
|
|
759
|
+
return result
|
|
760
|
+
if op in {"build-native", "build-incremental", "merge-raw", "dedup-proposals", "build-accepted"}:
|
|
761
|
+
sources, extractions, accepted = {}, [], None
|
|
762
|
+
if op == "build-accepted":
|
|
763
|
+
require(callable(self.accepted_owner), "ACCEPTANCE_OWNER", "Canonical owner verifier required; caller receipts cannot confer acceptance")
|
|
764
|
+
accepted = self.accepted_owner(copy.deepcopy(p.get("activation_ref")))
|
|
765
|
+
require(isinstance(accepted, AcceptedProjection) and accepted.history_digest and accepted.activation_ref and accepted.qualified_records,
|
|
766
|
+
"ACCEPTANCE_UNQUALIFIED", "Current qualified canonical records and head required")
|
|
767
|
+
require(re.fullmatch(r"sha256:[a-f0-9]{64}", accepted.history_digest) and
|
|
768
|
+
accepted.activation_ref == p.get("activation_ref") and
|
|
769
|
+
isinstance(accepted.activation_ref.get("id"), str) and
|
|
770
|
+
re.fullmatch(r"sha256:[a-f0-9]{64}", accepted.activation_ref.get("digest", "")),
|
|
771
|
+
"ACCEPTANCE_BINDING", "Existing canonical head/activation reference must match selected request")
|
|
772
|
+
require(all(isinstance(record, dict) and record.get("schema") == "atelier-knowledge-record@v1"
|
|
773
|
+
and record.get("kind") in {"contribution", "relation"}
|
|
774
|
+
for record in accepted.qualified_records)
|
|
775
|
+
and {r["id"] for r in accepted.qualified_records} == set(accepted.record_ids),
|
|
776
|
+
"ACCEPTANCE_RECORDS", "Qualified existing canonical records must cover retained IDs")
|
|
777
|
+
sources, extractions = self._sources(accepted.source_ids), copy.deepcopy(accepted.extractions)
|
|
778
|
+
ids = [n["id"] for ext in extractions for n in ext.get("nodes", [])]
|
|
779
|
+
require(len(ids) == len(set(ids)) and len(accepted.record_ids) == len(set(accepted.record_ids))
|
|
780
|
+
and set(ids) == set(accepted.record_ids), "ACCEPTANCE_IDENTITY", "Accepted projection must retain existing qualified record IDs")
|
|
781
|
+
settings = {"dedup": False, "directed": True, "protected_ids": set(ids)}
|
|
782
|
+
require(not p.get("settings"), "ACCEPTANCE_SETTING", "Accepted IDs/direction cannot be overridden")
|
|
783
|
+
receipt["acceptedHead"] = accepted.history_digest
|
|
784
|
+
receipt["artifacts"].append(self._write(directory, "qualified-records.json", accepted.qualified_records))
|
|
785
|
+
else:
|
|
786
|
+
for gid in p.get("extraction_ids", []):
|
|
787
|
+
item = self._fresh(gid, "extraction")
|
|
788
|
+
sources.update(item["dependencies"])
|
|
789
|
+
extractions.append(copy.deepcopy(item["value"]))
|
|
790
|
+
require(extractions or op in {"build-incremental", "merge-raw"} and bool(p.get("prune_sources")),
|
|
791
|
+
"EXTRACTION_REQUIRED", "Captured extraction generations or explicit source pruning required")
|
|
792
|
+
settings = self._settings(p, {"directed", "dedup", "dedup_llm_backend", "protected_ids"} if op in {"build-native", "build-incremental"} else {"communities", "dedup_llm_backend", "protected_ids"} if op == "dedup-proposals" else set())
|
|
793
|
+
receipt["settings"] = json_safe(settings)
|
|
794
|
+
receipt["artifacts"].append(self._write(directory, "raw-prebuild.json", extractions))
|
|
795
|
+
if op == "dedup-proposals":
|
|
796
|
+
raw = {k: sum((e.get(k, []) for e in extractions), []) for k in ("nodes", "edges", "hyperedges")}
|
|
797
|
+
result = self._invoke(op, raw["nodes"], raw["edges"], hyperedges=raw["hyperedges"], root=self.root, **{"communities": {}, **settings})
|
|
798
|
+
receipt["identityAcceptance"] = "pending"
|
|
799
|
+
receipt["rewiredHyperedges"] = json_safe(raw["hyperedges"])
|
|
800
|
+
return result
|
|
801
|
+
if op in {"build-incremental", "merge-raw"}:
|
|
802
|
+
previous = self._retained(p.get("graph_id"), "graph")
|
|
803
|
+
require(previous.get("view") != "accepted-projection", "ACCEPTANCE_SETTING", "Accepted projection requires fresh canonical owner rebuild")
|
|
804
|
+
require(previous.get("native_json"), "NATIVE_EXPORT_REQUIRED", "Incremental native API requires verified JSON export")
|
|
805
|
+
prune = p.get("prune_sources", [])
|
|
806
|
+
allowed_paths = {s["path"] for s in previous["dependencies"].values()} | {s["path"] for s in sources.values()}
|
|
807
|
+
require(isinstance(prune, list) and set(prune) <= allowed_paths, "PRUNE_SCOPE", "Prune only bound source paths")
|
|
808
|
+
ast_sources = p.get("ast_sources")
|
|
809
|
+
require(ast_sources is None or isinstance(ast_sources, list)
|
|
810
|
+
and set(ast_sources) <= {s["path"] for s in sources.values()},
|
|
811
|
+
"AST_SOURCE_SCOPE", "AST provenance must be among freshly captured selected sources")
|
|
812
|
+
# A historical graph is intentionally usable for maintenance.
|
|
813
|
+
# Every surviving old binding must be unchanged or re-extracted.
|
|
814
|
+
freshly_captured = set(sources)
|
|
815
|
+
for sid, descriptor in previous["dependencies"].items():
|
|
816
|
+
if descriptor["path"] in prune or sid in sources:
|
|
817
|
+
continue
|
|
818
|
+
current = self._sources([sid])[sid]
|
|
819
|
+
require(current == descriptor, "INCREMENTAL_STALE", "Changed source must be replaced or explicitly pruned")
|
|
820
|
+
sources[sid] = descriptor
|
|
821
|
+
# Native pruning removes the old version before the new chunks
|
|
822
|
+
# replace it. The replacement's current dependency must survive.
|
|
823
|
+
sources = {sid: s for sid, s in sources.items() if s["path"] not in prune or sid in freshly_captured}
|
|
824
|
+
native_path = previous["native_json"]["path"]
|
|
825
|
+
require(hashlib.sha256(Path(native_path).read_bytes()).hexdigest() == previous["native_json"]["sha256"], "ARTIFACT_DRIFT", "Native export changed")
|
|
826
|
+
if op == "merge-raw":
|
|
827
|
+
combined = {k: sum((e.get(k, []) for e in extractions), []) for k in ("nodes", "edges", "hyperedges")}
|
|
828
|
+
raw = self._invoke(op, combined, native_path, prune_sources=prune, root=self.root, ast_sources=ast_sources)
|
|
829
|
+
self._generation("extraction", copy.deepcopy(raw), sources, receipt)
|
|
830
|
+
return raw
|
|
831
|
+
graph = self._invoke(op, extractions, graph_path=native_path, prune_sources=prune, root=self.root, ast_sources=ast_sources, **settings)
|
|
832
|
+
else:
|
|
833
|
+
graph = self._invoke("build-native", extractions, root=self.root, **settings)
|
|
834
|
+
receipt["artifacts"].append(self._write(directory, "native-build-output.json", graph))
|
|
835
|
+
receipt["limitations"].append("Native simple build can coalesce parallel predicates; exact assertions/qualifiers remain in raw/qualified records. Stored orientation and logical traversal differ.")
|
|
836
|
+
extras = {"view": "accepted-projection" if accepted else "source-exploration"}
|
|
837
|
+
if accepted:
|
|
838
|
+
require(set(graph.nodes) == set(accepted.record_ids), "ACCEPTANCE_IDENTITY", "Native build changed qualified accepted IDs")
|
|
839
|
+
extras.update(accepted_reference=copy.deepcopy(p.get("activation_ref")), accepted_digest=digest(accepted.__dict__))
|
|
840
|
+
self._generation("graph", graph, sources, receipt, **extras)
|
|
841
|
+
return graph
|
|
842
|
+
if op == "graph-diff":
|
|
843
|
+
old, new = self._retained(p["old_graph_id"], "graph"), self._fresh(p["graph_id"], "graph")
|
|
844
|
+
receipt["graphGenerations"] = [p["old_graph_id"], p["graph_id"]]
|
|
845
|
+
receipt["comparisonScope"] = "historical-to-current derived graphs"
|
|
846
|
+
return self._invoke(op, copy.deepcopy(old["value"]), copy.deepcopy(new["value"]))
|
|
847
|
+
if op == "reflect":
|
|
848
|
+
require(p.get("memory_dir") is not None, "MEMORY_SCOPE", "Explicit owned work-memory directory required")
|
|
849
|
+
memory = Path(p["memory_dir"]).resolve()
|
|
850
|
+
require(memory.is_relative_to(self.artifacts) and memory.is_dir(), "MEMORY_SCOPE", "Work-memory must be an owned supplier artifact")
|
|
851
|
+
require(not any(path.is_symlink() for path in memory.rglob("*")), "MEMORY_SCOPE", "Work-memory symlinks are not admitted")
|
|
852
|
+
settings = self._settings(p, {"half_life_days", "min_corroboration"})
|
|
853
|
+
receipt["settings"] = json_safe(settings)
|
|
854
|
+
receipt["limitations"].append("Reflected work-memory lessons are derivative, not independent original evidence.")
|
|
855
|
+
output = directory / "lessons.md"
|
|
856
|
+
result = self._invoke(op, memory, output, **settings)
|
|
857
|
+
require(output.is_file() and not output.is_symlink(), "EXPORT_READBACK", "Native reflection output missing")
|
|
858
|
+
raw = output.read_bytes()
|
|
859
|
+
receipt["artifacts"].append({"path": str(output), "sha256": hashlib.sha256(raw).hexdigest(), "bytes": len(raw)})
|
|
860
|
+
return result
|
|
861
|
+
item = self._fresh(p["graph_id"], "graph")
|
|
862
|
+
# No downstream native analyzer or exporter owns the retained build
|
|
863
|
+
# generation. Preserve every original attribute without stripping it.
|
|
864
|
+
graph = copy.deepcopy(item["value"])
|
|
865
|
+
receipt["graphGeneration"], receipt["view"] = p["graph_id"], item["view"]
|
|
866
|
+
receipt["sourceBindings"] = item["dependencies"]
|
|
867
|
+
# Native text query cannot establish which passage supports a claim.
|
|
868
|
+
receipt["portableContext"] = {"scope": "retained-graph inventory, not selected-result evidence",
|
|
869
|
+
"nodes": [self._mapping({"id": nid, **attrs}, item["dependencies"]) for nid, attrs in graph.nodes(data=True)]}
|
|
870
|
+
allowed = {
|
|
871
|
+
"cluster": {"resolution", "exclude_hubs_percentile"}, "important-nodes": {"top_n", "exclude_hubs_percentile"},
|
|
872
|
+
"surprising-connections": {"top_n"}, "suggest-questions": {"top_n", "community_labels"},
|
|
873
|
+
"import-cycles": {"max_cycle_length", "top_n"}, "impact": {"relations", "depth"},
|
|
874
|
+
"query": {"mode", "depth", "token_budget", "context_filters"}, "path": {"max_hops", "undirected"},
|
|
875
|
+
"node": set(), "neighbors": set(), "label-communities": set(), "cohesion": set(),
|
|
876
|
+
"export-json": {"force", "built_at_commit", "community_labels"}, "export-canvas": {"community_labels", "node_filenames"},
|
|
877
|
+
"export-obsidian": {"community_labels", "cohesion"}, "export-svg": {"community_labels", "figsize"},
|
|
878
|
+
"export-graphml": set(), "export-cypher": set(),
|
|
879
|
+
}[op]
|
|
880
|
+
settings = self._settings(p, allowed)
|
|
881
|
+
receipt["settings"] = json_safe(settings)
|
|
882
|
+
communities = copy.deepcopy(p.get("communities", item.get("communities", {})))
|
|
883
|
+
if op == "cluster":
|
|
884
|
+
result = self._invoke(op, graph, **settings)
|
|
885
|
+
item["communities"] = copy.deepcopy(result)
|
|
886
|
+
return result
|
|
887
|
+
if op in {"label-communities", "cohesion", "surprising-connections"}:
|
|
888
|
+
return self._invoke(op, graph, communities=communities, **settings)
|
|
889
|
+
if op == "suggest-questions":
|
|
890
|
+
return self._invoke(op, graph, communities=communities, **{"community_labels": {}, **settings})
|
|
891
|
+
if op in {"important-nodes", "import-cycles"}:
|
|
892
|
+
return self._invoke(op, graph, **settings)
|
|
893
|
+
if op == "impact":
|
|
894
|
+
return self._invoke(op, graph, p["seed"], **settings)
|
|
895
|
+
if op == "query":
|
|
896
|
+
require(settings.get("mode", "bfs") in {"bfs", "dfs"}, "QUERY_MODE", "Native BFS or DFS required")
|
|
897
|
+
require(type(settings.get("depth", 3)) is int and 1 <= settings.get("depth", 3) <= 6, "QUERY_BOUND", "Bounded traversal required")
|
|
898
|
+
require(type(settings.get("token_budget", 2000)) is int and 1 <= settings.get("token_budget", 2000) <= 100000,
|
|
899
|
+
"QUERY_BOUND", "Bounded native query context required")
|
|
900
|
+
require(isinstance(p.get("question"), str) and 0 < len(p["question"]) <= 8192, "QUERY_BOUND", "Bounded query text required")
|
|
901
|
+
return self._read_graph_native(op, graph, directory, receipt, p["question"], **settings)
|
|
902
|
+
if op == "path":
|
|
903
|
+
return self._read_graph_native(op, graph, directory, receipt, {"source": p["source"], "target": p["target"], **settings})
|
|
904
|
+
if op in {"node", "neighbors"}:
|
|
905
|
+
node_id, message = self._read_graph_native(op, graph, directory, receipt, p["label"])
|
|
906
|
+
receipt["nativeLookup"] = json_safe((node_id, message))
|
|
907
|
+
ids = [node_id] if node_id is not None else []
|
|
908
|
+
if message is not None:
|
|
909
|
+
receipt["limitations"].append("Native lookup returned no unique winner; its original ambiguity/no-match message is retained.")
|
|
910
|
+
return [{"id": nid, "attributes": copy.deepcopy(graph.nodes[nid]),
|
|
911
|
+
"neighbors": list(graph.adj[nid].items()) if op == "neighbors" else None} for nid in ids]
|
|
912
|
+
suffix = {"export-json": "json", "export-cypher": "cypher", "export-canvas": "canvas", "export-graphml": "graphml", "export-svg": "svg", "export-obsidian": "obsidian"}[op]
|
|
913
|
+
if op == "export-graphml":
|
|
914
|
+
receipt["limitations"].append("Native GraphML omits underscore-prefixed node/edge markers, converts non-scalar fields to strings and sanitizes XML-illegal characters; original native attributes remain in the retained graph/captures.")
|
|
915
|
+
destination = directory / f"native.{suffix}"
|
|
916
|
+
if op == "export-cypher":
|
|
917
|
+
native = self._invoke(op, graph, str(destination), **settings)
|
|
918
|
+
elif op == "export-obsidian":
|
|
919
|
+
native = self._invoke(op, graph, communities, str(destination), **settings)
|
|
920
|
+
else:
|
|
921
|
+
native = self._invoke(op, graph, communities, str(destination), **settings)
|
|
922
|
+
files = list(destination.rglob("*")) if destination.is_dir() else [destination]
|
|
923
|
+
artifacts = []
|
|
924
|
+
for path in files:
|
|
925
|
+
require(not path.is_symlink() and path.resolve().is_relative_to(directory), "EXPORT_SCOPE", "Native export escaped owned destination")
|
|
926
|
+
if path.is_file():
|
|
927
|
+
raw = path.read_bytes()
|
|
928
|
+
artifacts.append({"path": str(path), "sha256": hashlib.sha256(raw).hexdigest(), "bytes": len(raw)})
|
|
929
|
+
require(artifacts, "EXPORT_READBACK", "Native export did not produce readable artifacts")
|
|
930
|
+
receipt["artifacts"].extend(artifacts)
|
|
931
|
+
if op == "export-json":
|
|
932
|
+
item["native_json"] = artifacts[0]
|
|
933
|
+
return {"nativeReturn": native, "exports": artifacts}
|