@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.
Files changed (560) hide show
  1. package/CHANGELOG.md +1005 -0
  2. package/README.md +149 -9
  3. package/contracts/atelier-adoption-policy.v2.schema.json +55 -0
  4. package/contracts/atelier-architecture.v1.schema.json +258 -0
  5. package/contracts/atelier-boundary-delegations.v1.schema.json +161 -0
  6. package/contracts/atelier-boundary-owner-keys.v1.schema.json +98 -0
  7. package/contracts/atelier-build.v1.schema.json +1076 -0
  8. package/contracts/atelier-capability.v1.schema.json +1252 -0
  9. package/contracts/atelier-coordination.v1.schema.json +1050 -0
  10. package/contracts/atelier-evidence-navigation.v1.schema.json +603 -0
  11. package/contracts/atelier-harness.v1.schema.json +234 -0
  12. package/contracts/atelier-ingestion.v1.schema.json +806 -0
  13. package/contracts/atelier-inquiry.v1.schema.json +1772 -0
  14. package/contracts/atelier-instruction-adoption.v1.schema.json +490 -0
  15. package/contracts/atelier-interaction.v1.schema.json +553 -0
  16. package/contracts/atelier-judgment.v1.schema.json +690 -0
  17. package/contracts/atelier-knowledge.v1.schema.json +1512 -0
  18. package/contracts/atelier-learning.v1.schema.json +1095 -0
  19. package/contracts/atelier-migration.v3.schema.json +64 -0
  20. package/contracts/atelier-obsidian-generation-manifest.v2.schema.json +372 -0
  21. package/contracts/atelier-personal-workspace-manifest.v1.schema.json +87 -0
  22. package/contracts/atelier-personal-workspace-overlay.v1.schema.json +299 -0
  23. package/contracts/atelier-template-adoption.v1.schema.json +223 -0
  24. package/contracts/atelier-template-profile.v1.schema.json +1168 -0
  25. package/contracts/atelier-trackable.v1.schema.json +781 -0
  26. package/contracts/atelier-upgrade-plan.v3.schema.json +532 -0
  27. package/contracts/compat-baseline.json +1 -1
  28. package/docs/architecture.md +89 -0
  29. package/docs/assurance-controls.md +4 -3
  30. package/docs/blocks/capability-stewardship.md +17 -0
  31. package/docs/blocks/claims.md +16 -3
  32. package/docs/blocks/inquiry-harnesses.md +19 -0
  33. package/docs/blocks/learning-harnesses.md +17 -0
  34. package/docs/capability-stewardship.md +269 -0
  35. package/docs/coordination.md +51 -0
  36. package/docs/decision-practices.md +111 -0
  37. package/docs/decisions.md +29 -0
  38. package/docs/evidence-navigation.md +237 -0
  39. package/docs/exact-upgrades.md +3 -2
  40. package/docs/ingestion.md +181 -0
  41. package/docs/inquiry-harnesses.md +211 -0
  42. package/docs/install.md +80 -3
  43. package/docs/intake-and-guides.md +5 -0
  44. package/docs/integration-contract-decisions.md +24 -2
  45. package/docs/interaction-and-reflection.md +72 -0
  46. package/docs/knowledge-graph.md +20 -0
  47. package/docs/knowledge-health/api.md +30 -0
  48. package/docs/knowledge-health/assessment/api.md +96 -0
  49. package/docs/knowledge-health/assessment/capabilities.md +40 -0
  50. package/docs/knowledge-health/assessment/evaluation.md +27 -0
  51. package/docs/knowledge-health/assessment/facilitator.md +30 -0
  52. package/docs/knowledge-health/assessment/quickstart.md +61 -0
  53. package/docs/knowledge-health/assessment/troubleshooting.md +23 -0
  54. package/docs/knowledge-health/devday/facilitator.md +16 -0
  55. package/docs/knowledge-health/devday/participant-response.md +22 -0
  56. package/docs/knowledge-health/devday/public-default.md +107 -0
  57. package/docs/knowledge-health/devday/quickstart.md +18 -0
  58. package/docs/knowledge-health/devday/support.md +22 -0
  59. package/docs/knowledge-health/exercise.md +17 -0
  60. package/docs/knowledge-health/facilitator.md +20 -0
  61. package/docs/knowledge-health/quickstart.md +94 -0
  62. package/docs/knowledge-health/readme.md +15 -0
  63. package/docs/knowledge-health/support.md +34 -0
  64. package/docs/knowledge-setup.md +248 -0
  65. package/docs/knowledge-workspace.md +264 -0
  66. package/docs/learning-harnesses.md +238 -0
  67. package/docs/learning.md +215 -0
  68. package/docs/local-services.md +242 -26
  69. package/docs/obsidian-contract.md +878 -36
  70. package/docs/obsidian-plugin.md +619 -0
  71. package/docs/obsidian.md +809 -64
  72. package/docs/optional-ingestion.md +83 -0
  73. package/docs/personal-workspace.md +257 -0
  74. package/docs/practical-judgment.md +92 -0
  75. package/docs/progressive-ingestion-acceptance.md +89 -0
  76. package/docs/progressive-ingestion.md +235 -0
  77. package/docs/project-command-inventory.md +1 -0
  78. package/docs/release-engineering.md +56 -1
  79. package/docs/repo-boundary-guard.md +94 -0
  80. package/docs/research-starter-quickstart.md +66 -0
  81. package/docs/research-starter.md +114 -0
  82. package/docs/semantic-ingestion.md +198 -0
  83. package/docs/semantic-operation.md +317 -0
  84. package/docs/skill-steward.md +6 -0
  85. package/docs/template-profile.md +164 -0
  86. package/docs/template-upgrades.md +114 -0
  87. package/docs/trackables.md +111 -0
  88. package/docs/upgrade.md +68 -3
  89. package/fixtures/atelier-architecture/binding/invalid/document.json +19 -0
  90. package/fixtures/atelier-architecture/binding/valid/document.json +19 -0
  91. package/fixtures/atelier-architecture/catalog/invalid/document.json +290 -0
  92. package/fixtures/atelier-architecture/catalog/valid/document.json +343 -0
  93. package/fixtures/atelier-build-contract/attempt/invalid/document.json +20 -0
  94. package/fixtures/atelier-build-contract/attempt/valid/document.json +19 -0
  95. package/fixtures/atelier-build-contract/candidate/invalid/document.json +24 -0
  96. package/fixtures/atelier-build-contract/candidate/valid/document.json +23 -0
  97. package/fixtures/atelier-build-contract/decision/invalid/document.json +31 -0
  98. package/fixtures/atelier-build-contract/decision/valid/document.json +30 -0
  99. package/fixtures/atelier-build-contract/delivery/invalid/document.json +18 -0
  100. package/fixtures/atelier-build-contract/delivery/valid/document.json +17 -0
  101. package/fixtures/atelier-build-contract/gate/invalid/document.json +22 -0
  102. package/fixtures/atelier-build-contract/gate/valid/document.json +21 -0
  103. package/fixtures/atelier-build-contract/ledger/invalid/document.json +270 -0
  104. package/fixtures/atelier-build-contract/ledger/valid/document.json +269 -0
  105. package/fixtures/atelier-build-contract/objective/invalid/document.json +73 -0
  106. package/fixtures/atelier-build-contract/objective/valid/document.json +72 -0
  107. package/fixtures/atelier-build-contract/progress/invalid/document.json +18 -0
  108. package/fixtures/atelier-build-contract/progress/valid/document.json +17 -0
  109. package/fixtures/atelier-capability-contract/adoption/invalid/document.json +71 -0
  110. package/fixtures/atelier-capability-contract/adoption/valid/document.json +70 -0
  111. package/fixtures/atelier-capability-contract/event/invalid/document.json +18 -0
  112. package/fixtures/atelier-capability-contract/event/valid/document.json +17 -0
  113. package/fixtures/atelier-capability-contract/journal/invalid/document.json +382 -0
  114. package/fixtures/atelier-capability-contract/journal/valid/document.json +381 -0
  115. package/fixtures/atelier-capability-contract/notice/invalid/document.json +8 -0
  116. package/fixtures/atelier-capability-contract/notice/valid/document.json +7 -0
  117. package/fixtures/atelier-capability-contract/package/invalid/document.json +59 -0
  118. package/fixtures/atelier-capability-contract/package/valid/document.json +58 -0
  119. package/fixtures/atelier-capability-contract/plan/invalid/document.json +107 -0
  120. package/fixtures/atelier-capability-contract/plan/valid/document.json +106 -0
  121. package/fixtures/atelier-capability-contract/release/invalid/document.json +85 -0
  122. package/fixtures/atelier-capability-contract/release/valid/document.json +84 -0
  123. package/fixtures/atelier-capability-contract/state/invalid/document.json +316 -0
  124. package/fixtures/atelier-capability-contract/state/valid/document.json +315 -0
  125. package/fixtures/atelier-coordination/dependency/invalid/document.json +37 -0
  126. package/fixtures/atelier-coordination/dependency/valid/document.json +36 -0
  127. package/fixtures/atelier-coordination/directive/invalid/document.json +25 -0
  128. package/fixtures/atelier-coordination/directive/valid/document.json +24 -0
  129. package/fixtures/atelier-coordination/disposition/invalid/document.json +34 -0
  130. package/fixtures/atelier-coordination/disposition/valid/document.json +33 -0
  131. package/fixtures/atelier-coordination/outcome/invalid/document.json +39 -0
  132. package/fixtures/atelier-coordination/outcome/valid/document.json +38 -0
  133. package/fixtures/atelier-coordination/recovery/invalid/document.json +34 -0
  134. package/fixtures/atelier-coordination/recovery/valid/document.json +33 -0
  135. package/fixtures/atelier-harness-contract/handoff/invalid/document.json +25 -0
  136. package/fixtures/atelier-harness-contract/handoff/valid/document.json +24 -0
  137. package/fixtures/atelier-ingestion/invalid/authority.json +19 -0
  138. package/fixtures/atelier-ingestion/semantic-baseline.json +26 -0
  139. package/fixtures/atelier-ingestion/semantic-operation.json +75 -0
  140. package/fixtures/atelier-ingestion/valid/plan-input.json +18 -0
  141. package/fixtures/atelier-inquiry-contract/assessment/invalid/document.json +41 -0
  142. package/fixtures/atelier-inquiry-contract/assessment/valid/document.json +40 -0
  143. package/fixtures/atelier-inquiry-contract/bundle/invalid/document.json +51 -0
  144. package/fixtures/atelier-inquiry-contract/bundle/valid/document.json +50 -0
  145. package/fixtures/atelier-inquiry-contract/campaign/invalid/document.json +17 -0
  146. package/fixtures/atelier-inquiry-contract/campaign/valid/document.json +16 -0
  147. package/fixtures/atelier-inquiry-contract/decision/invalid/document.json +22 -0
  148. package/fixtures/atelier-inquiry-contract/decision/valid/document.json +21 -0
  149. package/fixtures/atelier-inquiry-contract/feedback/invalid/document.json +19 -0
  150. package/fixtures/atelier-inquiry-contract/feedback/valid/document.json +18 -0
  151. package/fixtures/atelier-inquiry-contract/handoff/invalid/document.json +92 -0
  152. package/fixtures/atelier-inquiry-contract/handoff/valid/document.json +91 -0
  153. package/fixtures/atelier-inquiry-contract/hypothesis/invalid/document.json +20 -0
  154. package/fixtures/atelier-inquiry-contract/hypothesis/valid/document.json +19 -0
  155. package/fixtures/atelier-inquiry-contract/ledger/invalid/document.json +680 -0
  156. package/fixtures/atelier-inquiry-contract/ledger/valid/document.json +679 -0
  157. package/fixtures/atelier-inquiry-contract/legacy-assessment/invalid/document.json +19 -0
  158. package/fixtures/atelier-inquiry-contract/legacy-assessment/valid/document.json +18 -0
  159. package/fixtures/atelier-inquiry-contract/request/invalid/document.json +50 -0
  160. package/fixtures/atelier-inquiry-contract/request/valid/document.json +49 -0
  161. package/fixtures/atelier-inquiry-contract/source/invalid/document.json +18 -0
  162. package/fixtures/atelier-inquiry-contract/source/valid/document.json +17 -0
  163. package/fixtures/atelier-inquiry-contract/withdrawal/invalid/document.json +16 -0
  164. package/fixtures/atelier-inquiry-contract/withdrawal/valid/document.json +15 -0
  165. package/fixtures/atelier-instruction-adoption/context-receipt/invalid/document.json +18 -0
  166. package/fixtures/atelier-instruction-adoption/context-receipt/valid/document.json +17 -0
  167. package/fixtures/atelier-instruction-adoption/plan/invalid/document.json +63 -0
  168. package/fixtures/atelier-instruction-adoption/plan/valid/document.json +62 -0
  169. package/fixtures/atelier-instruction-adoption/receipt/invalid/document.json +22 -0
  170. package/fixtures/atelier-instruction-adoption/receipt/valid/document.json +21 -0
  171. package/fixtures/atelier-instruction-adoption/state/invalid/document.json +30 -0
  172. package/fixtures/atelier-instruction-adoption/state/valid/document.json +29 -0
  173. package/fixtures/atelier-interaction/act/invalid/document.json +25 -0
  174. package/fixtures/atelier-interaction/act/valid/document.json +24 -0
  175. package/fixtures/atelier-interaction/assessment/invalid/document.json +32 -0
  176. package/fixtures/atelier-interaction/assessment/valid/document.json +31 -0
  177. package/fixtures/atelier-interaction/observation/invalid/document.json +22 -0
  178. package/fixtures/atelier-interaction/observation/valid/document.json +21 -0
  179. package/fixtures/atelier-interaction/policy/invalid/document.json +20 -0
  180. package/fixtures/atelier-interaction/policy/valid/document.json +19 -0
  181. package/fixtures/atelier-judgment/case/invalid/document.json +76 -0
  182. package/fixtures/atelier-judgment/case/valid/document.json +75 -0
  183. package/fixtures/atelier-judgment/decision/invalid/document.json +38 -0
  184. package/fixtures/atelier-judgment/decision/valid/document.json +37 -0
  185. package/fixtures/atelier-judgment/native-decision.md +3 -0
  186. package/fixtures/atelier-judgment/practice-reconsideration/definition.json +92 -0
  187. package/fixtures/atelier-judgment/practice-reconsideration/scenario.json +435 -0
  188. package/fixtures/atelier-knowledge-contract/activation/invalid/document.json +26 -0
  189. package/fixtures/atelier-knowledge-contract/activation/valid/document.json +25 -0
  190. package/fixtures/atelier-knowledge-contract/contribution/invalid/document.json +34 -0
  191. package/fixtures/atelier-knowledge-contract/contribution/valid/document.json +33 -0
  192. package/fixtures/atelier-knowledge-contract/domain/invalid/document.json +41 -0
  193. package/fixtures/atelier-knowledge-contract/domain/valid/document.json +40 -0
  194. package/fixtures/atelier-knowledge-contract/domain-revision/invalid/document.json +46 -0
  195. package/fixtures/atelier-knowledge-contract/domain-revision/valid/document.json +45 -0
  196. package/fixtures/atelier-knowledge-contract/evaluation/invalid/document.json +21 -0
  197. package/fixtures/atelier-knowledge-contract/evaluation/valid/document.json +20 -0
  198. package/fixtures/atelier-knowledge-contract/ledger/invalid/document.json +318 -0
  199. package/fixtures/atelier-knowledge-contract/ledger/valid/document.json +317 -0
  200. package/fixtures/atelier-knowledge-contract/relation/invalid/document.json +25 -0
  201. package/fixtures/atelier-knowledge-contract/relation/valid/document.json +24 -0
  202. package/fixtures/atelier-knowledge-contract/review/invalid/document.json +18 -0
  203. package/fixtures/atelier-knowledge-contract/review/valid/document.json +17 -0
  204. package/fixtures/atelier-knowledge-contract/withdrawal/invalid/document.json +16 -0
  205. package/fixtures/atelier-knowledge-contract/withdrawal/valid/document.json +15 -0
  206. package/fixtures/atelier-learning/invalid/authority.json +14 -0
  207. package/fixtures/atelier-learning/valid/capture.json +13 -0
  208. package/fixtures/atelier-personal-workspace-contract/manifest/invalid/authority-key.json +17 -0
  209. package/fixtures/atelier-personal-workspace-contract/manifest/invalid/nonempty-ext.json +25 -0
  210. package/fixtures/atelier-personal-workspace-contract/manifest/invalid/wrong-schema.json +22 -0
  211. package/fixtures/atelier-personal-workspace-contract/manifest/valid/manifest.json +22 -0
  212. package/fixtures/atelier-personal-workspace-contract/overlay/invalid/authority-key.json +25 -0
  213. package/fixtures/atelier-personal-workspace-contract/overlay/invalid/unknown-top-level-field.json +27 -0
  214. package/fixtures/atelier-personal-workspace-contract/overlay/invalid/wrong-schema.json +24 -0
  215. package/fixtures/atelier-trackable/definition/invalid/document.json +15 -0
  216. package/fixtures/atelier-trackable/definition/valid/document.json +14 -0
  217. package/fixtures/atelier-trackable/request/invalid/document.json +23 -0
  218. package/fixtures/atelier-trackable/request/valid/document.json +22 -0
  219. package/fixtures/boundary-delegations/invalid/authority-key.v1.json +30 -0
  220. package/fixtures/boundary-delegations/invalid/push-operation.v1.json +28 -0
  221. package/fixtures/boundary-delegations/invalid/unsigned.v1.json +25 -0
  222. package/fixtures/boundary-delegations/valid/empty.v1.json +4 -0
  223. package/fixtures/boundary-delegations/valid/signed-operator.v1.json +30 -0
  224. package/fixtures/boundary-owner-keys/invalid/wrong-algorithm.v1.json +15 -0
  225. package/fixtures/boundary-owner-keys/invalid/wrong-curve.v1.json +15 -0
  226. package/fixtures/boundary-owner-keys/valid/one-owner.v1.json +17 -0
  227. package/fixtures/capability-packages/adoption.example.json +70 -0
  228. package/fixtures/capability-packages/evidence-review/capability-package.json +58 -0
  229. package/fixtures/capability-packages/evidence-review/capability-release.json +84 -0
  230. package/fixtures/capability-packages/evidence-review/evaluation.md +15 -0
  231. package/fixtures/capability-packages/evidence-review/skills/evidence-review/SKILL.md +22 -0
  232. package/fixtures/capability-packages/evidence-review/skills/evidence-review/references/ledger.md +7 -0
  233. package/fixtures/capability-packages/research/capability-package.json +88 -0
  234. package/fixtures/capability-packages/research/capability-release.json +134 -0
  235. package/fixtures/capability-packages/research/evaluation.md +15 -0
  236. package/fixtures/capability-packages/research/skills/research-integrate/SKILL.md +29 -0
  237. package/fixtures/capability-packages/research/skills/research-integrate/references/integration.md +10 -0
  238. package/fixtures/capability-packages/research/skills/research-plan/SKILL.md +27 -0
  239. package/fixtures/capability-packages/research/skills/research-plan/references/inquiries.md +26 -0
  240. package/fixtures/capability-packages/research/skills/research-synthesize/SKILL.md +23 -0
  241. package/fixtures/capability-packages/research/skills/research-synthesize/references/synthesis.md +12 -0
  242. package/fixtures/evidence-navigation/invalid/duplicate-operation.json +16 -0
  243. package/fixtures/evidence-navigation/invalid/instruction-kind.json +36 -0
  244. package/fixtures/evidence-navigation/invalid/invalid-digest.json +36 -0
  245. package/fixtures/evidence-navigation/invalid/invalid-time.json +36 -0
  246. package/fixtures/evidence-navigation/invalid/missing-attribution.json +35 -0
  247. package/fixtures/evidence-navigation/invalid/reserved-extension.json +19 -0
  248. package/fixtures/evidence-navigation/invalid/unattributed-acceptance.json +36 -0
  249. package/fixtures/evidence-navigation/invalid/uncoerced-version.json +16 -0
  250. package/fixtures/evidence-navigation/invalid/unknown-currency.json +17 -0
  251. package/fixtures/evidence-navigation/invalid/unknown-selector-field.json +37 -0
  252. package/fixtures/evidence-navigation/invalid/unsupported-operation.json +15 -0
  253. package/fixtures/evidence-navigation/valid/claim.json +36 -0
  254. package/fixtures/evidence-navigation/valid/host.json +16 -0
  255. package/fixtures/evidence-navigation/valid/profile.json +16 -0
  256. package/fixtures/evidence-navigation/valid/snapshot.json +17 -0
  257. package/fixtures/harness-packages/build-harness/capability-package.json +61 -0
  258. package/fixtures/harness-packages/build-harness/capability-release.json +82 -0
  259. package/fixtures/harness-packages/build-harness/evaluation.md +3 -0
  260. package/fixtures/harness-packages/build-harness/skills/atelier-build-harness/SKILL.md +59 -0
  261. package/fixtures/harness-packages/knowledge-harness/capability-package.json +61 -0
  262. package/fixtures/harness-packages/knowledge-harness/capability-release.json +82 -0
  263. package/fixtures/harness-packages/knowledge-harness/evaluation.md +3 -0
  264. package/fixtures/harness-packages/knowledge-harness/skills/atelier-knowledge-harness/SKILL.md +60 -0
  265. package/fixtures/harnesses/learning-cycle.json +780 -0
  266. package/fixtures/inquiry/workshop.json +675 -0
  267. package/fixtures/inquiry-packages/discovery-harness/capability-package.json +61 -0
  268. package/fixtures/inquiry-packages/discovery-harness/capability-release.json +82 -0
  269. package/fixtures/inquiry-packages/discovery-harness/evaluation.md +3 -0
  270. package/fixtures/inquiry-packages/discovery-harness/skills/atelier-discovery-harness/SKILL.md +48 -0
  271. package/fixtures/inquiry-packages/research-harness/capability-package.json +61 -0
  272. package/fixtures/inquiry-packages/research-harness/capability-release.json +82 -0
  273. package/fixtures/inquiry-packages/research-harness/evaluation.md +3 -0
  274. package/fixtures/inquiry-packages/research-harness/skills/atelier-research-harness/SKILL.md +43 -0
  275. package/fixtures/knowledge-health/calibration-suite.json +26 -0
  276. package/fixtures/knowledge-health/calibration-trial.json +37 -0
  277. package/fixtures/knowledge-health/corrected/checklist.md +18 -0
  278. package/fixtures/knowledge-health/devday/participant-record.json +19 -0
  279. package/fixtures/knowledge-health/expected-summary.json +27 -0
  280. package/fixtures/knowledge-health/workshop/README.md +20 -0
  281. package/fixtures/knowledge-health/workshop/atelier.project.json +7 -0
  282. package/fixtures/knowledge-health/workshop/knowledge-plan.json +65 -0
  283. package/fixtures/knowledge-health/workshop/records/checklist.md +18 -0
  284. package/fixtures/knowledge-health/workshop/records/workshop.md +18 -0
  285. package/fixtures/knowledge-health/workshop/repo-access.v1.json +5 -0
  286. package/fixtures/knowledge-health/workspace/atelier.project.json +18 -0
  287. package/fixtures/knowledge-health/workspace/knowledge-plan.json +74 -0
  288. package/fixtures/knowledge-health/workspace/records/checklist.md +18 -0
  289. package/fixtures/knowledge-health/workspace/records/pilot.md +16 -0
  290. package/fixtures/knowledge-health/workspace/repo-access.v1.json +9 -0
  291. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/case-only-collision.v2.json +160 -0
  292. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/flat-note-path.v2.json +160 -0
  293. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/hidden-folder.v2.json +160 -0
  294. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/identity-in-the-body.v2.json +160 -0
  295. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/link-breaking-name.v2.json +160 -0
  296. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/missing-identity-region.v2.json +156 -0
  297. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/missing-layout-version.v2.json +159 -0
  298. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/note-named-like-a-folder.v2.json +160 -0
  299. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/unknown-region-kind.v2.json +160 -0
  300. package/fixtures/obsidian/contracts/generation-manifest-v2/invalid/v1-contract-version.v2.json +161 -0
  301. package/fixtures/obsidian/contracts/generation-manifest-v2/valid/three-notes.v2.json +160 -0
  302. package/fixtures/obsidian/edits/workspace.json +1 -1
  303. package/fixtures/obsidian/materialization/expected-full.json +21 -21
  304. package/fixtures/obsidian/materialization/expected-full.layout-1.json +99 -0
  305. package/fixtures/obsidian/materialization/expected-scoped.json +7 -7
  306. package/fixtures/obsidian/materialization/expected-scoped.layout-1.json +39 -0
  307. package/fixtures/personal-workspace/overlay.json +24 -0
  308. package/fixtures/templates/local-library.v1.json +75 -0
  309. package/fixtures/templates/upgrade-contracts/adoption-policy.v2/invalid.json +16 -0
  310. package/fixtures/templates/upgrade-contracts/adoption-policy.v2/valid.json +13 -0
  311. package/fixtures/templates/upgrade-contracts/migration.v3/invalid.json +24 -0
  312. package/fixtures/templates/upgrade-contracts/migration.v3/valid.json +21 -0
  313. package/fixtures/templates/upgrade-contracts/template-adoption.v1/invalid.json +60 -0
  314. package/fixtures/templates/upgrade-contracts/template-adoption.v1/valid.json +57 -0
  315. package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/invalid.json +85 -0
  316. package/fixtures/templates/upgrade-contracts/upgrade-plan.v3/valid.json +82 -0
  317. package/package.json +64 -6
  318. package/plugins/obsidian/main.js +444 -0
  319. package/plugins/obsidian/manifest.json +9 -0
  320. package/plugins/obsidian/styles.css +22 -0
  321. package/skills/claude/atelier-build-harness/SKILL.md +59 -0
  322. package/skills/claude/atelier-coordination/SKILL.md +32 -0
  323. package/skills/claude/atelier-discovery-harness/SKILL.md +48 -0
  324. package/skills/claude/atelier-guided-upgrade/SKILL.md +29 -0
  325. package/skills/claude/atelier-interaction/SKILL.md +34 -0
  326. package/skills/claude/atelier-knowledge-harness/SKILL.md +60 -0
  327. package/skills/claude/atelier-knowledge-setup/SKILL.md +72 -0
  328. package/skills/claude/atelier-practical-judgment/SKILL.md +64 -0
  329. package/skills/claude/atelier-research-harness/SKILL.md +43 -0
  330. package/skills/claude/atelier-skill-steward/SKILL.md +53 -0
  331. package/skills/claude/atelier-trackables/SKILL.md +40 -0
  332. package/skills/codex/atelier-build-harness/SKILL.md +59 -0
  333. package/skills/codex/atelier-coordination/SKILL.md +32 -0
  334. package/skills/codex/atelier-discovery-harness/SKILL.md +48 -0
  335. package/skills/codex/atelier-guided-upgrade/SKILL.md +29 -0
  336. package/skills/codex/atelier-interaction/SKILL.md +34 -0
  337. package/skills/codex/atelier-knowledge-harness/SKILL.md +60 -0
  338. package/skills/codex/atelier-knowledge-setup/SKILL.md +72 -0
  339. package/skills/codex/atelier-practical-judgment/SKILL.md +64 -0
  340. package/skills/codex/atelier-research-harness/SKILL.md +43 -0
  341. package/skills/codex/atelier-skill-steward/SKILL.md +53 -0
  342. package/skills/codex/atelier-trackables/SKILL.md +40 -0
  343. package/src/architecture/catalog.mjs +355 -0
  344. package/src/architecture/index.mjs +93 -0
  345. package/src/boundary/content-rules.mjs +4 -2
  346. package/src/boundary/delegation-command.mjs +158 -0
  347. package/src/boundary/delegation.mjs +396 -0
  348. package/src/boundary/policy.mjs +52 -10
  349. package/src/build/git.mjs +18 -0
  350. package/src/build/index.mjs +3 -0
  351. package/src/build/ledger.mjs +88 -0
  352. package/src/capabilities/adoption.mjs +381 -0
  353. package/src/capabilities/evidence.mjs +140 -0
  354. package/src/capabilities/files.mjs +124 -0
  355. package/src/capabilities/index.mjs +4 -0
  356. package/src/capabilities/instructions.mjs +250 -0
  357. package/src/capabilities/package.mjs +136 -0
  358. package/src/cli/command-failure.mjs +36 -0
  359. package/src/cli/execute-command.mjs +16 -2
  360. package/src/cli/run.mjs +65 -9
  361. package/src/coauthor/store.mjs +43 -2
  362. package/src/collaboration/proposals.mjs +189 -0
  363. package/src/commands/architecture.mjs +27 -0
  364. package/src/commands/boundary.mjs +3 -0
  365. package/src/commands/capability.mjs +73 -0
  366. package/src/commands/enroll.mjs +3 -0
  367. package/src/commands/harness.mjs +79 -0
  368. package/src/commands/ingest.mjs +39 -0
  369. package/src/commands/init.mjs +4 -1
  370. package/src/commands/inquiry.mjs +66 -0
  371. package/src/commands/knowledge-semantic.mjs +128 -0
  372. package/src/commands/knowledge.mjs +119 -0
  373. package/src/commands/learn.mjs +61 -0
  374. package/src/commands/obsidian.mjs +832 -62
  375. package/src/commands/practice.mjs +24 -0
  376. package/src/commands/setup.mjs +22 -2
  377. package/src/commands/trackable.mjs +19 -0
  378. package/src/contracts/corpus.mjs +36 -0
  379. package/src/coordination/index.mjs +110 -0
  380. package/src/egress/forbidden-egress.mjs +2 -0
  381. package/src/evidence-navigation/contracts.mjs +60 -0
  382. package/src/evidence-navigation/evaluate.mjs +69 -0
  383. package/src/evidence-navigation/generation.mjs +22 -0
  384. package/src/evidence-navigation/index.mjs +2 -0
  385. package/src/evidence-navigation/local.mjs +167 -0
  386. package/src/evidence-navigation/schema.generated.mjs +604 -0
  387. package/src/evidence-navigation/types.mjs +12 -0
  388. package/src/export/atelier-export-contract.mjs +35 -8
  389. package/src/graph/enroll.mjs +248 -0
  390. package/src/graph/graph.mjs +17 -1
  391. package/src/graph/knowledge-graph.mjs +58 -8
  392. package/src/harnesses/contracts.mjs +41 -0
  393. package/src/harnesses/exchange.mjs +154 -0
  394. package/src/harnesses/feedback.mjs +16 -0
  395. package/src/harnesses/history.mjs +37 -0
  396. package/src/harnesses/index.mjs +4 -0
  397. package/src/harnesses/store.mjs +29 -0
  398. package/src/ingestion/contracts.mjs +41 -0
  399. package/src/ingestion/evaluation.mjs +119 -0
  400. package/src/ingestion/optional/composition/index.mjs +171 -0
  401. package/src/ingestion/optional/evaluation/index.mjs +219 -0
  402. package/src/ingestion/optional/graphify/composition-binding.mjs +60 -0
  403. package/src/ingestion/optional/graphify/composition_receiving.py +185 -0
  404. package/src/ingestion/optional/graphify/graphify_supplier.py +933 -0
  405. package/src/ingestion/optional/graphify/json_bridge.py +163 -0
  406. package/src/ingestion/optional/graphify/query_receiving.py +209 -0
  407. package/src/ingestion/optional/index.mjs +9 -0
  408. package/src/ingestion/optional/jev/bridge.mjs +166 -0
  409. package/src/ingestion/optional/jev/composition-binding.mjs +116 -0
  410. package/src/ingestion/optional/jev/judgments.mjs +174 -0
  411. package/src/ingestion/optional/jev/native-translation.mjs +32 -0
  412. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/__init__.py +7 -0
  413. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/adapter.py +966 -0
  414. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/artifacts.py +101 -0
  415. package/src/ingestion/optional/lightrag/atelier_lightrag_supplier/projection.py +146 -0
  416. package/src/ingestion/optional/lightrag/composition-binding.mjs +67 -0
  417. package/src/ingestion/optional/vanilla/composition-binding.mjs +102 -0
  418. package/src/ingestion/optional/vanilla/index.mjs +218 -0
  419. package/src/ingestion/optional/vanilla/native-normalization.mjs +187 -0
  420. package/src/ingestion/processors.mjs +218 -0
  421. package/src/ingestion/semantic.mjs +203 -0
  422. package/src/ingestion/store.mjs +372 -0
  423. package/src/inquiry/beliefs.mjs +32 -0
  424. package/src/inquiry/contracts.mjs +23 -0
  425. package/src/inquiry/index.mjs +5 -0
  426. package/src/inquiry/ledger.mjs +135 -0
  427. package/src/inquiry/lenses.mjs +15 -0
  428. package/src/inquiry/projection.mjs +77 -0
  429. package/src/inquiry/store.mjs +35 -0
  430. package/src/intake/read-scope.mjs +12 -0
  431. package/src/intake/store.mjs +69 -6
  432. package/src/interaction/index.mjs +91 -0
  433. package/src/judgment/index.mjs +82 -0
  434. package/src/judgment/practice-evaluation.mjs +70 -0
  435. package/src/judgment/practice.mjs +144 -0
  436. package/src/knowledge/context.mjs +217 -0
  437. package/src/knowledge/index.mjs +5 -0
  438. package/src/knowledge/ingestion.mjs +79 -0
  439. package/src/knowledge/intake.mjs +33 -0
  440. package/src/knowledge/ledger.mjs +110 -0
  441. package/src/knowledge/participatory/guidance.mjs +36 -0
  442. package/src/knowledge/participatory/guided-contribution.mjs +44 -0
  443. package/src/knowledge/participatory/index.mjs +2 -0
  444. package/src/knowledge/participatory/installed-workshop.mjs +108 -0
  445. package/src/knowledge/plan.mjs +67 -0
  446. package/src/knowledge/projection.mjs +43 -0
  447. package/src/knowledge/semantic-operation.mjs +567 -0
  448. package/src/knowledge/sessions.mjs +467 -0
  449. package/src/knowledge/workspace.mjs +295 -0
  450. package/src/knowledge-health/assessment.mjs +165 -0
  451. package/src/knowledge-health/capabilities.mjs +350 -0
  452. package/src/knowledge-health/context.mjs +62 -0
  453. package/src/knowledge-health/example.mjs +47 -0
  454. package/src/knowledge-health/index.mjs +10 -0
  455. package/src/knowledge-health/owner-join.mjs +88 -0
  456. package/src/knowledge-health/proof/common.mjs +24 -0
  457. package/src/knowledge-health/proof/installed-check.mjs +100 -0
  458. package/src/knowledge-health/proof/native-readback.mjs +37 -0
  459. package/src/knowledge-health/proof/rehearse.mjs +68 -0
  460. package/src/knowledge-health/proof/validate-participant.mjs +17 -0
  461. package/src/knowledge-health/proof/verify-native-capture.mjs +14 -0
  462. package/src/knowledge-health/proof/workshop-check.mjs +36 -0
  463. package/src/knowledge-health/rule-identity.mjs +44 -0
  464. package/src/knowledge-health/workshop-composition.mjs +19 -0
  465. package/src/knowledge-health/workshop-profile.mjs +176 -0
  466. package/src/knowledge-health/workshop.mjs +99 -0
  467. package/src/learning/adapters.mjs +68 -0
  468. package/src/learning/assistance.mjs +127 -0
  469. package/src/learning/contracts.mjs +65 -0
  470. package/src/learning/store.mjs +272 -0
  471. package/src/personal-workspace/index.mjs +391 -0
  472. package/src/personal-workspace/selection.mjs +385 -0
  473. package/src/project/config.mjs +8 -0
  474. package/src/project/durable-state.mjs +3 -1
  475. package/src/project/file-class.mjs +1 -1
  476. package/src/project/private-state.mjs +10 -1
  477. package/src/projection/obsidian/contracts.mjs +224 -18
  478. package/src/projection/obsidian/edits/apply.mjs +63 -19
  479. package/src/projection/obsidian/edits/regions.mjs +137 -42
  480. package/src/projection/obsidian/materialize/index.mjs +6 -3
  481. package/src/projection/obsidian/materialize/path-registry.mjs +443 -57
  482. package/src/projection/obsidian/materialize/prepare-view.mjs +382 -131
  483. package/src/projection/obsidian/materialize/redaction.mjs +327 -0
  484. package/src/projection/obsidian/materialize/settings.mjs +113 -34
  485. package/src/projection/obsidian/personal-workspace.mjs +436 -0
  486. package/src/projection/obsidian/plugin-bridge/bundle.mjs +50 -0
  487. package/src/projection/obsidian/plugin-bridge/channel.mjs +174 -0
  488. package/src/projection/obsidian/plugin-bridge/index.mjs +10 -0
  489. package/src/projection/obsidian/proposals/adapter.mjs +47 -8
  490. package/src/projection/obsidian/proposals/observation.mjs +20 -7
  491. package/src/projection/obsidian/publication/bridge-script.mjs +11 -5
  492. package/src/projection/obsidian/publication/index.mjs +2 -1
  493. package/src/projection/obsidian/publication/publisher.mjs +312 -36
  494. package/src/projection/obsidian/publication/transport.mjs +63 -8
  495. package/src/projection/obsidian/publication/vault-list.mjs +200 -0
  496. package/src/projection/obsidian/recovery/index.mjs +4 -1
  497. package/src/projection/obsidian/recovery/restart.mjs +3 -1
  498. package/src/projection/obsidian/recovery/store.mjs +168 -8
  499. package/src/projection/obsidian/selection-ui/contribution.mjs +6 -5
  500. package/src/projection/obsidian/selection-ui/focus.mjs +14 -7
  501. package/src/projection/obsidian/selection-ui/selection-state.mjs +2 -2
  502. package/src/projection/obsidian/selection-ui/selection.mjs +8 -3
  503. package/src/readiness/readiness.mjs +10 -3
  504. package/src/reflection/index.mjs +56 -0
  505. package/src/runtime/obsidian/app-capability.mjs +104 -12
  506. package/src/runtime/obsidian/app-production-seams.mjs +161 -42
  507. package/src/runtime/obsidian/app-registration.mjs +313 -0
  508. package/src/runtime/obsidian/app-restart.mjs +154 -0
  509. package/src/runtime/obsidian/engine.mjs +288 -29
  510. package/src/runtime/obsidian/extension-points.mjs +1 -1
  511. package/src/runtime/obsidian/index.mjs +15 -8
  512. package/src/runtime/obsidian/launch-plan.mjs +91 -0
  513. package/src/runtime/obsidian/lifecycle.mjs +193 -22
  514. package/src/runtime/obsidian/login-item.mjs +332 -0
  515. package/src/runtime/obsidian/machine-settings.mjs +119 -12
  516. package/src/runtime/obsidian/opening.mjs +369 -63
  517. package/src/runtime/obsidian/pending-edits.mjs +10 -0
  518. package/src/runtime/obsidian/pipeline.mjs +71 -3
  519. package/src/runtime/obsidian/plugin-channel.mjs +289 -0
  520. package/src/runtime/obsidian/plugin-choice.mjs +182 -0
  521. package/src/runtime/obsidian/plugin-drift.mjs +124 -0
  522. package/src/runtime/obsidian/plugin-presence.mjs +61 -0
  523. package/src/runtime/obsidian/process-identity.mjs +20 -10
  524. package/src/runtime/obsidian/project-views.mjs +282 -0
  525. package/src/runtime/obsidian/questions.mjs +48 -0
  526. package/src/runtime/obsidian/release-watch.mjs +56 -0
  527. package/src/runtime/obsidian/service-main.mjs +113 -17
  528. package/src/runtime/obsidian/service-manager-production.mjs +61 -0
  529. package/src/runtime/obsidian/service-managers.mjs +275 -0
  530. package/src/runtime/obsidian/service-record.mjs +105 -5
  531. package/src/runtime/obsidian/service-server.mjs +50 -12
  532. package/src/runtime/obsidian/service.mjs +134 -7
  533. package/src/runtime/obsidian/startup-adapters.mjs +59 -21
  534. package/src/runtime/obsidian/state-store.mjs +13 -1
  535. package/src/runtime/obsidian/unheld-evidence.mjs +84 -0
  536. package/src/runtime/obsidian/vault-location.mjs +300 -0
  537. package/src/runtime/obsidian/view-counts.mjs +34 -0
  538. package/src/server/local-sidecar.mjs +193 -7
  539. package/src/server/server.mjs +45 -6
  540. package/src/template-bindings/project.mjs +92 -0
  541. package/src/templates/profile.mjs +225 -0
  542. package/src/trackables/domain.mjs +175 -0
  543. package/src/trackables/store.mjs +114 -0
  544. package/src/ui/knowledge-page.mjs +1388 -0
  545. package/src/upgrade/explanation.mjs +1 -1
  546. package/src/upgrade/template-participant.mjs +171 -0
  547. package/src/upgrade/transaction.mjs +72 -25
  548. package/src/upgrade/upgrade.mjs +2 -1
  549. package/templates/external-project-workspace/pack-lifecycle.json +6 -1
  550. package/templates/knowledge-workspace/.gitattributes +2 -0
  551. package/templates/knowledge-workspace/AGENTS.md +27 -0
  552. package/templates/knowledge-workspace/README.md +44 -0
  553. package/templates/knowledge-workspace/atelier.project.json +22 -0
  554. package/templates/knowledge-workspace/gitignore +6 -0
  555. package/templates/knowledge-workspace/knowledge-plan.json +111 -0
  556. package/templates/knowledge-workspace/knowledge-plan.schema.json +281 -0
  557. package/templates/knowledge-workspace/records/forecast.md +22 -0
  558. package/templates/knowledge-workspace/records/inspection.md +25 -0
  559. package/templates/knowledge-workspace/records/telescope.md +24 -0
  560. 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}