@ornexus/neocortex 4.60.73 → 4.60.74

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 (241) hide show
  1. package/dist/release-source.json +1 -1
  2. package/dist/sbom.cdx.json +1216 -1210
  3. package/install.ps1 +1 -1
  4. package/install.sh +1 -1
  5. package/package.json +2 -2
  6. package/packages/client/dist/adapters/adapter-registry.js +1 -108
  7. package/packages/client/dist/adapters/antigravity-adapter.js +2 -77
  8. package/packages/client/dist/adapters/claude-code-adapter.js +3 -79
  9. package/packages/client/dist/adapters/codex-adapter.js +2 -80
  10. package/packages/client/dist/adapters/cursor-adapter.js +4 -115
  11. package/packages/client/dist/adapters/gemini-adapter.js +2 -71
  12. package/packages/client/dist/adapters/grok-adapter.js +2 -42
  13. package/packages/client/dist/adapters/index.js +1 -22
  14. package/packages/client/dist/adapters/platform-detector.js +1 -212
  15. package/packages/client/dist/adapters/target-adapter.js +0 -12
  16. package/packages/client/dist/adapters/vscode-adapter.js +2 -72
  17. package/packages/client/dist/agent/refresh-stubs.js +2 -417
  18. package/packages/client/dist/agent/update-agent-yaml.js +1 -102
  19. package/packages/client/dist/agent/update-description.js +1 -251
  20. package/packages/client/dist/cache/crypto-utils.js +1 -78
  21. package/packages/client/dist/cache/encrypted-cache.js +1 -108
  22. package/packages/client/dist/cache/in-memory-asset-cache.js +1 -73
  23. package/packages/client/dist/cache/index.js +1 -13
  24. package/packages/client/dist/cache/protected-pi-boundary.js +1 -44
  25. package/packages/client/dist/checkpoint/checkpoint-client-reader.js +2 -127
  26. package/packages/client/dist/checkpoint/index.js +1 -11
  27. package/packages/client/dist/checkpoint/shared-checkpoint-types.js +1 -32
  28. package/packages/client/dist/cli.js +5 -358
  29. package/packages/client/dist/commands/activate.js +8 -394
  30. package/packages/client/dist/commands/architecture-answers.js +7 -274
  31. package/packages/client/dist/commands/architecture-memory-terminal.js +4 -253
  32. package/packages/client/dist/commands/architecture-non-ui-continuation.js +1 -100
  33. package/packages/client/dist/commands/architecture-root-binding.js +1 -51
  34. package/packages/client/dist/commands/cache-status.js +2 -112
  35. package/packages/client/dist/commands/config.js +1 -88
  36. package/packages/client/dist/commands/doctor.js +2 -635
  37. package/packages/client/dist/commands/invoke.js +143 -7200
  38. package/packages/client/dist/commands/legacy-epic-terminal.js +1 -71
  39. package/packages/client/dist/commands/legacy-story-branch.js +1 -96
  40. package/packages/client/dist/commands/legacy-story-pr.js +1 -276
  41. package/packages/client/dist/commands/mutation-qa-checkpoint.js +5 -285
  42. package/packages/client/dist/commands/mutation-qa-gate.js +1 -54
  43. package/packages/client/dist/commands/mutation-qa-git.js +1 -41
  44. package/packages/client/dist/commands/mutation-qa-policy.js +1 -102
  45. package/packages/client/dist/commands/mutation-qa-run.js +3 -219
  46. package/packages/client/dist/commands/mutation-qa-verdict.js +1 -149
  47. package/packages/client/dist/commands/mutation-qa.js +1 -220
  48. package/packages/client/dist/commands/native-agent.js +2 -136
  49. package/packages/client/dist/commands/planning-diagnosis.js +1 -43
  50. package/packages/client/dist/commands/planning-generation.js +1 -53
  51. package/packages/client/dist/commands/planning-memory.js +1 -40
  52. package/packages/client/dist/commands/planning-research.js +1 -42
  53. package/packages/client/dist/commands/private-session.js +1 -75
  54. package/packages/client/dist/commands/property-qa.js +1 -139
  55. package/packages/client/dist/commands/qa-control.js +1 -16
  56. package/packages/client/dist/commands/qa-scan-browser-launch.js +1 -55
  57. package/packages/client/dist/commands/qa-scan-browser-route.js +1 -28
  58. package/packages/client/dist/commands/qa-scan-capture.js +5 -663
  59. package/packages/client/dist/commands/qa-scan-discovery.js +3 -376
  60. package/packages/client/dist/commands/qa-scan-review.js +5 -826
  61. package/packages/client/dist/commands/qa-synthetic-backend-capture.js +1 -266
  62. package/packages/client/dist/commands/qa-synthetic-backend-review.js +1 -297
  63. package/packages/client/dist/commands/qa-synthetic-backend.js +2 -340
  64. package/packages/client/dist/commands/qa-synthetic-broker-port.js +1 -85
  65. package/packages/client/dist/commands/qa-synthetic-browser.js +1 -39
  66. package/packages/client/dist/commands/refactor-characterization.js +21 -671
  67. package/packages/client/dist/commands/refactor-commit.js +13 -399
  68. package/packages/client/dist/commands/refactor-control.js +3 -142
  69. package/packages/client/dist/commands/refactor-cpp-assertions.js +3 -93
  70. package/packages/client/dist/commands/refactor-review.js +5 -283
  71. package/packages/client/dist/commands/refactor-root.js +7 -588
  72. package/packages/client/dist/commands/refresh-memory.js +3 -156
  73. package/packages/client/dist/commands/shared-architecture-non-ui.js +1 -24
  74. package/packages/client/dist/commands/story-checkpoint.js +1 -171
  75. package/packages/client/dist/config/profile-directory.js +1 -13
  76. package/packages/client/dist/config/resolver-selection.js +1 -278
  77. package/packages/client/dist/config/secure-config.js +12 -281
  78. package/packages/client/dist/config.js +2 -223
  79. package/packages/client/dist/constants.js +1 -25
  80. package/packages/client/dist/context/context-collector.js +2 -222
  81. package/packages/client/dist/context/context-sanitizer.js +1 -145
  82. package/packages/client/dist/continuity/autonomous-governance.js +1 -285
  83. package/packages/client/dist/continuity/continuity-client-state-store.js +1 -1587
  84. package/packages/client/dist/continuity/invoke-hooks.js +1 -129
  85. package/packages/client/dist/continuity/migrations/001-initial-schema.js +2 -12
  86. package/packages/client/dist/continuity/migrations/002-processing-output-summary-indexes.js +2 -12
  87. package/packages/client/dist/continuity/migrations/003-headless-execution-attempts.js +2 -12
  88. package/packages/client/dist/continuity/migrations/004-outbox-delivery-state.js +2 -12
  89. package/packages/client/dist/continuity/migrations/005-bounded-headless-queue.js +2 -12
  90. package/packages/client/dist/continuity/migrations/006-native-agent-dispatches.js +2 -10
  91. package/packages/client/dist/continuity/native-agent-store.js +1 -69
  92. package/packages/client/dist/continuity/native-agent-types.js +1 -90
  93. package/packages/client/dist/continuity/outbox.js +1 -117
  94. package/packages/client/dist/continuity/runner-bootstrap-policy.js +1 -172
  95. package/packages/client/dist/continuity/sqlite-store.js +81 -2612
  96. package/packages/client/dist/effects/invoke-effect-gateways.js +1 -62
  97. package/packages/client/dist/errors/error-messages.js +2 -228
  98. package/packages/client/dist/errors/frontmatter-diagnostics.js +1 -125
  99. package/packages/client/dist/evidence/collector.js +1 -347
  100. package/packages/client/dist/evidence/index.js +1 -1
  101. package/packages/client/dist/evidence/types.js +0 -2
  102. package/packages/client/dist/fs/atomic-state-replace.js +1 -152
  103. package/packages/client/dist/generated/command-registry.generated.js +1 -4076
  104. package/packages/client/dist/generated/public-projections.generated.js +1 -149
  105. package/packages/client/dist/git/direct-yolo-c01-retry.js +4 -138
  106. package/packages/client/dist/git/direct-yolo-checkpoint.js +4 -1234
  107. package/packages/client/dist/git/epic-operational-state.js +2 -672
  108. package/packages/client/dist/git/git-lifecycle-step-ids.js +1 -14
  109. package/packages/client/dist/git/git-lifecycle-store.d.ts +23 -0
  110. package/packages/client/dist/git/git-lifecycle-store.js +10 -1981
  111. package/packages/client/dist/git/legacy-story-branch-checkpoint.js +9 -259
  112. package/packages/client/dist/git/legacy-story-branch-setup.js +4 -348
  113. package/packages/client/dist/git/legacy-story-pr.js +70 -3538
  114. package/packages/client/dist/git/secure-filesystem-boundary.js +5 -182
  115. package/packages/client/dist/graph-retrieval/pre-command-hook.js +1 -284
  116. package/packages/client/dist/graph-retrieval/shared-graph-retrieval-contract.js +1 -237
  117. package/packages/client/dist/i18n/first-run.js +2 -60
  118. package/packages/client/dist/index.js +1 -47
  119. package/packages/client/dist/invocation/invoke-input.js +1 -24
  120. package/packages/client/dist/license/index.js +1 -5
  121. package/packages/client/dist/license/license-client.js +1 -304
  122. package/packages/client/dist/machine/fingerprint.js +2 -163
  123. package/packages/client/dist/machine/index.js +1 -5
  124. package/packages/client/dist/memory/project-memory-path-scope.js +1 -62
  125. package/packages/client/dist/memory/project-memory-writer.js +40 -1007
  126. package/packages/client/dist/memory/shared-project-memory-types.js +2 -253
  127. package/packages/client/dist/migrations/epic-lifecycle-backfill.js +3 -1060
  128. package/packages/client/dist/migrations/project-identity-capability.js +1 -50
  129. package/packages/client/dist/migrations/project-identity.js +7 -493
  130. package/packages/client/dist/migrations/state-json.js +4 -393
  131. package/packages/client/dist/policy/architecture-policy.js +2 -184
  132. package/packages/client/dist/policy/index.js +1 -7
  133. package/packages/client/dist/policy/shared-policy-types.js +0 -38
  134. package/packages/client/dist/policy/subagent-approval-policy.js +1 -55
  135. package/packages/client/dist/private-execution/clean-baseline.js +2 -279
  136. package/packages/client/dist/private-execution/codex-executor.js +4 -574
  137. package/packages/client/dist/private-execution/codex-host-runtime.js +1 -137
  138. package/packages/client/dist/private-execution/codex-lifecycle-guard.js +1 -97
  139. package/packages/client/dist/private-execution/codex-scoped-permissions.js +1 -128
  140. package/packages/client/dist/private-execution/delivery.js +13 -410
  141. package/packages/client/dist/private-execution/diagnosis-root-review.js +2 -294
  142. package/packages/client/dist/private-execution/frozen-handoff.js +6 -19
  143. package/packages/client/dist/private-execution/implementation-validation.js +2 -106
  144. package/packages/client/dist/private-execution/isolated-admission.js +23 -1432
  145. package/packages/client/dist/private-execution/isolated-session.js +5 -725
  146. package/packages/client/dist/private-execution/isolated-workspace.js +1 -122
  147. package/packages/client/dist/private-execution/pact-consumer-runtime.js +3 -409
  148. package/packages/client/dist/private-execution/pact-test-source.js +1 -258
  149. package/packages/client/dist/private-execution/pact-toolchain.js +3 -290
  150. package/packages/client/dist/private-execution/pact-write-set.js +1 -172
  151. package/packages/client/dist/private-execution/planning-diagnosis-checkpoint.js +5 -406
  152. package/packages/client/dist/private-execution/planning-generation-checkpoint.js +5 -462
  153. package/packages/client/dist/private-execution/planning-memory-checkpoint.js +3 -139
  154. package/packages/client/dist/private-execution/planning-research-checkpoint.js +4 -325
  155. package/packages/client/dist/private-execution/private-report-store.js +1 -42
  156. package/packages/client/dist/private-execution/property-test-candidate.js +1 -103
  157. package/packages/client/dist/private-execution/property-test-choices.js +3 -147
  158. package/packages/client/dist/private-execution/property-test-plan.js +7 -271
  159. package/packages/client/dist/private-execution/property-test-review.js +4 -258
  160. package/packages/client/dist/private-execution/property-test-runtime.js +9 -372
  161. package/packages/client/dist/private-execution/property-test-source.js +1 -139
  162. package/packages/client/dist/private-execution/root-checkpoint.js +1 -80
  163. package/packages/client/dist/private-execution/root-memory-git-evidence.js +3 -135
  164. package/packages/client/dist/private-execution/root-memory-recovery.js +1 -93
  165. package/packages/client/dist/private-execution/root-memory-request.js +1 -29
  166. package/packages/client/dist/private-execution/root-memory-scope.js +1 -178
  167. package/packages/client/dist/private-execution/root-memory-writer.js +1 -102
  168. package/packages/client/dist/private-execution/session-lease.js +1 -63
  169. package/packages/client/dist/private-execution/shared-private-admission-types.js +1 -50
  170. package/packages/client/dist/private-execution/shared-project-validation.js +1 -66
  171. package/packages/client/dist/private-execution/shared-root-memory-types.js +1 -61
  172. package/packages/client/dist/private-execution/ux-source-validation.js +1 -372
  173. package/packages/client/dist/private-execution/ux-write-set.js +2 -191
  174. package/packages/client/dist/registry/registry-reconciliation.js +1 -234
  175. package/packages/client/dist/resilience/circuit-breaker.js +1 -170
  176. package/packages/client/dist/resilience/degradation-manager.js +1 -164
  177. package/packages/client/dist/resilience/freshness-indicator.js +1 -100
  178. package/packages/client/dist/resilience/index.js +1 -8
  179. package/packages/client/dist/resilience/recovery-detector.js +1 -74
  180. package/packages/client/dist/resolvers/asset-resolver.js +0 -13
  181. package/packages/client/dist/resolvers/local-resolver.js +8 -255
  182. package/packages/client/dist/resolvers/remote-resolver.js +1 -233
  183. package/packages/client/dist/runner/binary.js +1 -404
  184. package/packages/client/dist/runner/cli.js +17 -1571
  185. package/packages/client/dist/runner/headless/adapters.js +1 -49
  186. package/packages/client/dist/runner/headless/claude-code-adapter.js +1 -79
  187. package/packages/client/dist/runner/headless/continuous-story-workspace.js +11 -1628
  188. package/packages/client/dist/runner/headless/epic-git-reconciliation-inventory.js +1 -69
  189. package/packages/client/dist/runner/headless/epic-merge-candidate-qa.d.ts +1 -1
  190. package/packages/client/dist/runner/headless/epic-merge-candidate-qa.js +2 -110
  191. package/packages/client/dist/runner/headless/epic-terminal-git-cli-adapter.d.ts +2 -0
  192. package/packages/client/dist/runner/headless/epic-terminal-git-cli-adapter.js +10 -1236
  193. package/packages/client/dist/runner/headless/epic-terminal-git-runner.d.ts +18 -2
  194. package/packages/client/dist/runner/headless/epic-terminal-git-runner.js +2 -318
  195. package/packages/client/dist/runner/headless/fake-adapter.js +1 -62
  196. package/packages/client/dist/runner/headless/managed-profile.js +1 -132
  197. package/packages/client/dist/runner/headless/opencode-adapter.js +2 -145
  198. package/packages/client/dist/runner/headless/prepared-story-bundle.js +1 -43
  199. package/packages/client/dist/runner/headless/resolver.js +1 -231
  200. package/packages/client/dist/runner/headless/run-yolo.js +1 -885
  201. package/packages/client/dist/runner/headless/run-yoloop.d.ts +1 -0
  202. package/packages/client/dist/runner/headless/run-yoloop.js +2 -2574
  203. package/packages/client/dist/runner/headless/types.js +2 -574
  204. package/packages/client/dist/runner/local-console.js +13 -396
  205. package/packages/client/dist/runner/portal-sync-worker.js +1 -1487
  206. package/packages/client/dist/runner/project-identity.js +1 -106
  207. package/packages/client/dist/runner/scheduler.js +7 -995
  208. package/packages/client/dist/runner-cli.js +2 -18
  209. package/packages/client/dist/state/invocation-shape.js +1 -13
  210. package/packages/client/dist/state/project-state-snapshot.js +1 -1731
  211. package/packages/client/dist/state/state-json-repair.js +3 -785
  212. package/packages/client/dist/state/structural-dag-projection.js +1 -118
  213. package/packages/client/dist/telemetry/index.js +1 -5
  214. package/packages/client/dist/telemetry/offline-queue.js +1 -131
  215. package/packages/client/dist/tier/index.js +1 -5
  216. package/packages/client/dist/tier/tier-aware-client.js +1 -405
  217. package/packages/client/dist/types/index.js +1 -38
  218. package/packages/client/dist/version.js +1 -27
  219. package/packages/client/dist/yolo/change-evidence-collector.js +1 -126
  220. package/packages/client/dist/yolo/invoke-envelope-capability.js +1 -62
  221. package/packages/client/dist/yolo/spec-envelope-collector.js +5 -184
  222. package/packages/client/dist/yoloop/discovery-hook.js +2 -300
  223. package/packages/client/dist/yoloop/index.js +1 -8
  224. package/packages/client/dist/yoloop/invoke-hooks.js +12 -669
  225. package/packages/client/dist/yoloop/shared-discover-epics.js +1 -1007
  226. package/packages/client/dist/yoloop/shared-yoloop-types.js +1 -33
  227. package/packages/client/dist/yoloop/target-normalization.js +1 -180
  228. package/packages/client/dist/yoloop/yoloop-client-state-store.js +1 -1920
  229. package/packages/shared/dist/yolo/change-evidence.js +1 -1
  230. package/packages/shared/dist/yoloop/epic-terminal-git.js +144 -2
  231. package/targets-stubs/antigravity/gemini.md +1 -1
  232. package/targets-stubs/antigravity/skill/SKILL.md +1 -1
  233. package/targets-stubs/claude-code/neocortex-root.agent.yaml +1 -1
  234. package/targets-stubs/claude-code/neocortex-root.md +2 -2
  235. package/targets-stubs/claude-code/neocortex.agent.yaml +1 -1
  236. package/targets-stubs/claude-code/neocortex.md +2 -2
  237. package/targets-stubs/codex/AGENTS.md +1 -1
  238. package/targets-stubs/cursor/agent.md +2 -2
  239. package/targets-stubs/gemini-cli/agent.md +2 -2
  240. package/targets-stubs/opencode/neocortex-root.md +1 -1
  241. package/targets-stubs/vscode/neocortex.agent.md +2 -2
@@ -1,103 +1 @@
1
- /** Root-owned read-only inspection after a private property-test candidate is applied. */
2
- import { createHash } from 'node:crypto';
3
- import { lstatSync, readFileSync } from 'node:fs';
4
- import { join } from 'node:path';
5
- import { isDeepStrictEqual } from 'node:util';
6
- import { mutationQaGit } from '../commands/mutation-qa-git.js';
7
- import { loadPropertyTestCandidatePlan } from './property-test-choices.js';
8
- import { inspectPropertyTestCatalog, renderPropertyTestConfiguration } from './property-test-plan.js';
9
- import { validatePropertyTestTypeScriptSource } from './property-test-source.js';
10
- function digest(value) {
11
- return createHash('sha256').update(value).digest('hex');
12
- }
13
- function regularBytes(root, relative) {
14
- let current = root;
15
- const parts = relative.split('/');
16
- if (parts.some((part) => !/^[A-Za-z0-9_.-]+$/.test(part) || part === '.' || part === '..')) {
17
- throw new Error('property-test-candidate-path-invalid');
18
- }
19
- for (const [index, part] of parts.entries()) {
20
- current = join(current, part);
21
- const stat = lstatSync(current);
22
- if (stat.isSymbolicLink() || (index === parts.length - 1
23
- ? !stat.isFile() || stat.size < 1 || stat.size > 2_000_000 : !stat.isDirectory())) {
24
- throw new Error('property-test-candidate-path-invalid');
25
- }
26
- }
27
- return readFileSync(current);
28
- }
29
- function status(root) {
30
- return mutationQaGit(root, ['status', '--porcelain=v1', '-z', '--untracked-files=all'])
31
- .toString('utf8').split('\0').filter(Boolean).sort();
32
- }
33
- /** No test execution, state write or semantic QA; only exact physical candidate bytes. */
34
- export function inspectPropertyTestCandidate(projectRoot) {
35
- try {
36
- const { root, head, choices, plan } = loadPropertyTestCandidatePlan(projectRoot);
37
- if (plan.schemaVersion !== 1 || !['prd.md', 'docs/architecture/prd.md'].includes(plan.prdPath)
38
- || plan.planSha256.length !== 64 || plan.writeSet.length < 1
39
- || plan.writeSet.length !== plan.aggregates.length
40
- || plan.checkpointAccepted !== false || plan.publicSafe !== true
41
- || plan.prdSha256 !== choices.prdSha256 || plan.language !== choices.language
42
- || plan.numRuns !== choices.numRuns || plan.aggregates.some((item) => choices.mappings[item.name] !== item.implementationFile
43
- || !Number.isSafeInteger(item.invariantCount) || item.invariantCount < 1)
44
- || !isDeepStrictEqual(plan.writeSet, plan.aggregates.map((item) => item.testFile).sort())) {
45
- throw new Error('property-test-candidate-plan-invalid');
46
- }
47
- const { planSha256: _sha, checkpointAccepted: _checkpoint, publicSafe: _publicSafe, ...basis } = plan;
48
- if (digest(JSON.stringify(basis)) !== plan.planSha256) {
49
- throw new Error('property-test-candidate-plan-invalid');
50
- }
51
- const catalog = inspectPropertyTestCatalog(root);
52
- if (catalog.prdPath !== plan.prdPath || !isDeepStrictEqual(catalog.aggregates.map(({ name, invariantCount }) => ({ name, invariantCount })), plan.aggregates.map(({ name, invariantCount }) => ({ name, invariantCount }))))
53
- throw new Error('property-test-candidate-plan-invalid');
54
- const trackedPrd = mutationQaGit(root, ['show', `HEAD:${plan.prdPath}`]);
55
- if (digest(trackedPrd) !== plan.prdSha256)
56
- throw new Error('property-test-candidate-preimage-drift');
57
- const currentPrd = regularBytes(root, plan.prdPath);
58
- const currentText = new TextDecoder('utf-8', { fatal: true }).decode(currentPrd);
59
- const dates = [...currentText.matchAll(/^- Generated at: (\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)$/gm)];
60
- if (dates.length !== 1 || currentText.split('## Property Test Configuration').length !== 2
61
- || currentText !== renderPropertyTestConfiguration(new TextDecoder('utf-8', { fatal: true }).decode(trackedPrd), plan, dates[0][1])) {
62
- throw new Error('property-test-candidate-prd-invalid');
63
- }
64
- const expectedPaths = [plan.prdPath, ...plan.writeSet].sort();
65
- const before = status(root);
66
- if (!isDeepStrictEqual(before.map((entry) => entry.slice(3)).sort(), expectedPaths)
67
- || before.some((entry) => ![' M ', '?? '].includes(entry.slice(0, 3)))) {
68
- throw new Error('property-test-candidate-scope-invalid');
69
- }
70
- const artifacts = [[plan.prdPath, digest(currentPrd)]];
71
- for (const item of plan.aggregates) {
72
- const implementation = regularBytes(root, item.implementationFile);
73
- if (digest(implementation) !== item.implementationSha256) {
74
- throw new Error('property-test-candidate-preimage-drift');
75
- }
76
- const source = new TextDecoder('utf-8', { fatal: true })
77
- .decode(regularBytes(root, item.testFile));
78
- if (plan.language === 'typescript') {
79
- validatePropertyTestTypeScriptSource({ source, testFile: item.testFile,
80
- implementationFile: item.implementationFile, invariantCount: item.invariantCount,
81
- numRuns: plan.numRuns });
82
- }
83
- else if ((source.match(/^def\s+test_[A-Za-z0-9_]+\s*\(/gm) ?? []).length < item.invariantCount
84
- || !source.includes(String(plan.numRuns))) {
85
- throw new Error('property-test-candidate-source-invalid');
86
- }
87
- artifacts.push([item.testFile, digest(source)]);
88
- }
89
- if (mutationQaGit(root, ['rev-parse', '--verify', 'HEAD']).toString('utf8').trim() !== head
90
- || !isDeepStrictEqual(status(root), before)
91
- || artifacts.some(([path, sha]) => digest(regularBytes(root, path)) !== sha)) {
92
- throw new Error('property-test-candidate-physical-drift');
93
- }
94
- return Object.freeze({ schemaVersion: 1, kind: 'property-test-candidate-inspection',
95
- sourceHead: head, planSha256: plan.planSha256,
96
- artifactSha256: digest(`property-test-candidate-v1\0${JSON.stringify(artifacts)}`),
97
- testFileCount: plan.writeSet.length, sourceSyntaxChecked: plan.language === 'typescript',
98
- executionVerified: false, qaAccepted: false, publicSafe: true });
99
- }
100
- catch {
101
- throw new Error('property-test-candidate-unverified');
102
- }
103
- }
1
+ import{createHash as F}from"node:crypto";import{lstatSync as T,readFileSync as x}from"node:fs";import{join as b}from"node:path";import{isDeepStrictEqual as d}from"node:util";import{mutationQaGit as m}from"../commands/mutation-qa-git.js";import{loadPropertyTestCandidatePlan as k}from"./property-test-choices.js";import{inspectPropertyTestCatalog as A,renderPropertyTestConfiguration as _}from"./property-test-plan.js";import{validatePropertyTestTypeScriptSource as D}from"./property-test-source.js";function s(n){return F("sha256").update(n).digest("hex")}function l(n,r){let i=n;const a=r.split("/");if(a.some(t=>!/^[A-Za-z0-9_.-]+$/.test(t)||t==="."||t===".."))throw new Error("property-test-candidate-path-invalid");for(const[t,g]of a.entries()){i=b(i,g);const p=T(i);if(p.isSymbolicLink()||(t===a.length-1?!p.isFile()||p.size<1||p.size>2e6:!p.isDirectory()))throw new Error("property-test-candidate-path-invalid")}return x(i)}function P(n){return m(n,["status","--porcelain=v1","-z","--untracked-files=all"]).toString("utf8").split("\0").filter(Boolean).sort()}function q(n){try{const{root:r,head:i,choices:a,plan:t}=k(n);if(t.schemaVersion!==1||!["prd.md","docs/architecture/prd.md"].includes(t.prdPath)||t.planSha256.length!==64||t.writeSet.length<1||t.writeSet.length!==t.aggregates.length||t.checkpointAccepted!==!1||t.publicSafe!==!0||t.prdSha256!==a.prdSha256||t.language!==a.language||t.numRuns!==a.numRuns||t.aggregates.some(e=>a.mappings[e.name]!==e.implementationFile||!Number.isSafeInteger(e.invariantCount)||e.invariantCount<1)||!d(t.writeSet,t.aggregates.map(e=>e.testFile).sort()))throw new Error("property-test-candidate-plan-invalid");const{planSha256:g,checkpointAccepted:p,publicSafe:z,...E}=t;if(s(JSON.stringify(E))!==t.planSha256)throw new Error("property-test-candidate-plan-invalid");const y=A(r);if(y.prdPath!==t.prdPath||!d(y.aggregates.map(({name:e,invariantCount:o})=>({name:e,invariantCount:o})),t.aggregates.map(({name:e,invariantCount:o})=>({name:e,invariantCount:o}))))throw new Error("property-test-candidate-plan-invalid");const w=m(r,["show",`HEAD:${t.prdPath}`]);if(s(w)!==t.prdSha256)throw new Error("property-test-candidate-preimage-drift");const S=l(r,t.prdPath),u=new TextDecoder("utf-8",{fatal:!0}).decode(S),v=[...u.matchAll(/^- Generated at: (\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z)$/gm)];if(v.length!==1||u.split("## Property Test Configuration").length!==2||u!==_(new TextDecoder("utf-8",{fatal:!0}).decode(w),t,v[0][1]))throw new Error("property-test-candidate-prd-invalid");const C=[t.prdPath,...t.writeSet].sort(),f=P(r);if(!d(f.map(e=>e.slice(3)).sort(),C)||f.some(e=>![" M ","?? "].includes(e.slice(0,3))))throw new Error("property-test-candidate-scope-invalid");const h=[[t.prdPath,s(S)]];for(const e of t.aggregates){const o=l(r,e.implementationFile);if(s(o)!==e.implementationSha256)throw new Error("property-test-candidate-preimage-drift");const c=new TextDecoder("utf-8",{fatal:!0}).decode(l(r,e.testFile));if(t.language==="typescript")D({source:c,testFile:e.testFile,implementationFile:e.implementationFile,invariantCount:e.invariantCount,numRuns:t.numRuns});else if((c.match(/^def\s+test_[A-Za-z0-9_]+\s*\(/gm)??[]).length<e.invariantCount||!c.includes(String(t.numRuns)))throw new Error("property-test-candidate-source-invalid");h.push([e.testFile,s(c)])}if(m(r,["rev-parse","--verify","HEAD"]).toString("utf8").trim()!==i||!d(P(r),f)||h.some(([e,o])=>s(l(r,e))!==o))throw new Error("property-test-candidate-physical-drift");return Object.freeze({schemaVersion:1,kind:"property-test-candidate-inspection",sourceHead:i,planSha256:t.planSha256,artifactSha256:s(`property-test-candidate-v1\0${JSON.stringify(h)}`),testFileCount:t.writeSet.length,sourceSyntaxChecked:t.language==="typescript",executionVerified:!1,qaAccepted:!1,publicSafe:!0})}catch{throw new Error("property-test-candidate-unverified")}}export{q as inspectPropertyTestCandidate};
@@ -1,147 +1,3 @@
1
- /** Private, root-owned operator choices for one plan-bound property-test run. */
2
- import { createHash, randomUUID } from 'node:crypto';
3
- import { closeSync, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
4
- import { isAbsolute, join, resolve } from 'node:path';
5
- import { confirmPropertyTestPlan } from './property-test-plan.js';
6
- import { mutationQaGit } from '../commands/mutation-qa-git.js';
7
- function digest(value) {
8
- return createHash('sha256').update(value).digest('hex');
9
- }
10
- function git(root, args) {
11
- return mutationQaGit(root, args).toString('utf8').trim();
12
- }
13
- function context(projectRoot, create) {
14
- const root = resolve(projectRoot);
15
- const top = git(root, ['rev-parse', '--show-toplevel']);
16
- const head = git(root, ['rev-parse', '--verify', 'HEAD']);
17
- const commonRaw = git(root, ['rev-parse', '--git-common-dir']);
18
- const common = isAbsolute(commonRaw) ? resolve(commonRaw) : resolve(root, commonRaw);
19
- if (realpathSync(root) !== root || resolve(top) !== root
20
- || !/^[a-f0-9]{40,64}$/.test(head)
21
- || realpathSync(common) !== common || lstatSync(common).isSymbolicLink()
22
- || lstatSync(common).uid !== process.getuid?.()) {
23
- throw new Error('property-test-private-store-invalid');
24
- }
25
- const directory = join(common, 'neocortex-property-test');
26
- if (create)
27
- mkdirSync(directory, { recursive: true, mode: 0o700 });
28
- if (existsSync(directory)) {
29
- const entry = lstatSync(directory);
30
- if (!entry.isDirectory() || entry.isSymbolicLink()
31
- || entry.uid !== process.getuid?.() || (entry.mode & 0o077) !== 0) {
32
- throw new Error('property-test-private-store-invalid');
33
- }
34
- }
35
- return { root, directory, head };
36
- }
37
- function recordFor(head, choices, plan) {
38
- const normalized = {
39
- prdSha256: choices.prdSha256, language: choices.language, numRuns: choices.numRuns,
40
- mappings: Object.fromEntries(Object.entries(choices.mappings).sort(([a], [b]) => a.localeCompare(b))),
41
- };
42
- const payload = { schemaVersion: 'PropertyTestChoices/2',
43
- sourceHead: head, choices: normalized, planSha256: plan.planSha256, plan };
44
- return { ...payload, checksumSha256: digest(`property-test-choices-v2\0${JSON.stringify(payload)}`) };
45
- }
46
- function readRecord(path) {
47
- const entry = lstatSync(path);
48
- if (!entry.isFile() || entry.isSymbolicLink() || entry.uid !== process.getuid?.()
49
- || (entry.mode & 0o077) !== 0 || entry.size < 1 || entry.size > 32_768) {
50
- throw new Error('property-test-private-store-invalid');
51
- }
52
- const raw = readFileSync(path, 'utf8');
53
- const value = JSON.parse(raw);
54
- if (!value || typeof value !== 'object' || Array.isArray(value)) {
55
- throw new Error('property-test-private-store-invalid');
56
- }
57
- const record = value;
58
- const v2 = record.schemaVersion === 'PropertyTestChoices/2';
59
- if (Object.keys(record).sort().join(',') !== ['schemaVersion', 'sourceHead',
60
- 'choices', 'planSha256', ...(v2 ? ['plan'] : []), 'checksumSha256'].sort().join(',')
61
- || !['PropertyTestChoices/1', 'PropertyTestChoices/2'].includes(String(record.schemaVersion))
62
- || typeof record.sourceHead !== 'string' || !/^[a-f0-9]{40,64}$/.test(record.sourceHead)
63
- || typeof record.planSha256 !== 'string' || !/^[a-f0-9]{64}$/.test(record.planSha256)
64
- || typeof record.checksumSha256 !== 'string' || !/^[a-f0-9]{64}$/.test(record.checksumSha256)
65
- || !record.choices || typeof record.choices !== 'object' || Array.isArray(record.choices)
66
- || v2 && (!record.plan || typeof record.plan !== 'object' || Array.isArray(record.plan)
67
- || record.plan.planSha256 !== record.planSha256)) {
68
- throw new Error('property-test-private-store-invalid');
69
- }
70
- const payload = { schemaVersion: record.schemaVersion, sourceHead: record.sourceHead,
71
- choices: record.choices, planSha256: record.planSha256,
72
- ...(v2 ? { plan: record.plan } : {}) };
73
- if (record.checksumSha256 !== digest(`property-test-choices-v${v2 ? '2' : '1'}\0${JSON.stringify(payload)}`)
74
- || raw !== `${JSON.stringify(record)}\n`)
75
- throw new Error('property-test-private-store-invalid');
76
- return record;
77
- }
78
- /** Save an explicit local choice, replacing only this private record. No tracked file is touched. */
79
- export function savePropertyTestChoices(projectRoot, choices) {
80
- const { root, directory, head } = context(projectRoot, true);
81
- const plan = confirmPropertyTestPlan(root, choices);
82
- const record = recordFor(head, choices, plan);
83
- const target = join(directory, 'choices.json');
84
- if (existsSync(target)) {
85
- const previous = readRecord(target);
86
- if (JSON.stringify(previous) === JSON.stringify(record))
87
- return plan;
88
- }
89
- const temporary = join(directory, `choices-${randomUUID()}.tmp`);
90
- const handle = openSync(temporary, 'wx', 0o600);
91
- try {
92
- writeFileSync(handle, `${JSON.stringify(record)}\n`);
93
- fsyncSync(handle);
94
- }
95
- finally {
96
- closeSync(handle);
97
- }
98
- try {
99
- if (git(root, ['rev-parse', '--verify', 'HEAD']) !== head
100
- || confirmPropertyTestPlan(root, choices).planSha256 !== plan.planSha256) {
101
- throw new Error('property-test-choices-stale');
102
- }
103
- renameSync(temporary, target);
104
- const directoryHandle = openSync(directory, 'r');
105
- try {
106
- fsyncSync(directoryHandle);
107
- }
108
- finally {
109
- closeSync(directoryHandle);
110
- }
111
- }
112
- finally {
113
- if (existsSync(temporary))
114
- unlinkSync(temporary);
115
- }
116
- return plan;
117
- }
118
- /** Load only a plan still bound to the current physical HEAD and file preimages. */
119
- export function loadPropertyTestChoices(projectRoot) {
120
- const { root, directory, head } = context(projectRoot, false);
121
- if (!existsSync(directory))
122
- return undefined;
123
- const target = join(directory, 'choices.json');
124
- if (!existsSync(target))
125
- return undefined;
126
- const record = readRecord(target);
127
- if (record.sourceHead !== head)
128
- throw new Error('property-test-choices-stale');
129
- const plan = confirmPropertyTestPlan(root, record.choices);
130
- if (plan.planSha256 !== record.planSha256)
131
- throw new Error('property-test-choices-stale');
132
- if (record.plan && JSON.stringify(record.plan) !== JSON.stringify(plan)) {
133
- throw new Error('property-test-choices-stale');
134
- }
135
- return { choices: record.choices, plan };
136
- }
137
- /** A v2 plan remains readable after its authorized PRD/test patch; no acceptance is inferred. */
138
- export function loadPropertyTestCandidatePlan(projectRoot) {
139
- const { root, directory, head } = context(projectRoot, false);
140
- const target = join(directory, 'choices.json');
141
- if (!existsSync(target))
142
- throw new Error('property-test-choices-missing');
143
- const record = readRecord(target);
144
- if (record.sourceHead !== head || !record.plan)
145
- throw new Error('property-test-choices-stale');
146
- return { root, head, choices: record.choices, plan: record.plan };
147
- }
1
+ import{createHash as O,randomUUID as k}from"node:crypto";import{closeSync as w,existsSync as p,fsyncSync as v,lstatSync as l,mkdirSync as b,openSync as E,readFileSync as P,realpathSync as j,renameSync as $,unlinkSync as A,writeFileSync as J}from"node:fs";import{isAbsolute as N,join as y,resolve as f}from"node:path";import{confirmPropertyTestPlan as m}from"./property-test-plan.js";import{mutationQaGit as x}from"../commands/mutation-qa-git.js";function H(n){return O("sha256").update(n).digest("hex")}function d(n,t){return x(n,t).toString("utf8").trim()}function S(n,t){const o=f(n),s=d(o,["rev-parse","--show-toplevel"]),r=d(o,["rev-parse","--verify","HEAD"]),e=d(o,["rev-parse","--git-common-dir"]),i=N(e)?f(e):f(o,e);if(j(o)!==o||f(s)!==o||!/^[a-f0-9]{40,64}$/.test(r)||j(i)!==i||l(i).isSymbolicLink()||l(i).uid!==process.getuid?.())throw new Error("property-test-private-store-invalid");const a=y(i,"neocortex-property-test");if(t&&b(a,{recursive:!0,mode:448}),p(a)){const c=l(a);if(!c.isDirectory()||c.isSymbolicLink()||c.uid!==process.getuid?.()||(c.mode&63)!==0)throw new Error("property-test-private-store-invalid")}return{root:o,directory:a,head:r}}function C(n,t,o){const s={prdSha256:t.prdSha256,language:t.language,numRuns:t.numRuns,mappings:Object.fromEntries(Object.entries(t.mappings).sort(([e],[i])=>e.localeCompare(i)))},r={schemaVersion:"PropertyTestChoices/2",sourceHead:n,choices:s,planSha256:o.planSha256,plan:o};return{...r,checksumSha256:H(`property-test-choices-v2\0${JSON.stringify(r)}`)}}function g(n){const t=l(n);if(!t.isFile()||t.isSymbolicLink()||t.uid!==process.getuid?.()||(t.mode&63)!==0||t.size<1||t.size>32768)throw new Error("property-test-private-store-invalid");const o=P(n,"utf8"),s=JSON.parse(o);if(!s||typeof s!="object"||Array.isArray(s))throw new Error("property-test-private-store-invalid");const r=s,e=r.schemaVersion==="PropertyTestChoices/2";if(Object.keys(r).sort().join(",")!==["schemaVersion","sourceHead","choices","planSha256",...e?["plan"]:[],"checksumSha256"].sort().join(",")||!["PropertyTestChoices/1","PropertyTestChoices/2"].includes(String(r.schemaVersion))||typeof r.sourceHead!="string"||!/^[a-f0-9]{40,64}$/.test(r.sourceHead)||typeof r.planSha256!="string"||!/^[a-f0-9]{64}$/.test(r.planSha256)||typeof r.checksumSha256!="string"||!/^[a-f0-9]{64}$/.test(r.checksumSha256)||!r.choices||typeof r.choices!="object"||Array.isArray(r.choices)||e&&(!r.plan||typeof r.plan!="object"||Array.isArray(r.plan)||r.plan.planSha256!==r.planSha256))throw new Error("property-test-private-store-invalid");const i={schemaVersion:r.schemaVersion,sourceHead:r.sourceHead,choices:r.choices,planSha256:r.planSha256,...e?{plan:r.plan}:{}};if(r.checksumSha256!==H(`property-test-choices-v${e?"2":"1"}\0${JSON.stringify(i)}`)||o!==`${JSON.stringify(r)}
2
+ `)throw new Error("property-test-private-store-invalid");return r}function z(n,t){const{root:o,directory:s,head:r}=S(n,!0),e=m(o,t),i=C(r,t,e),a=y(s,"choices.json");if(p(a)){const h=g(a);if(JSON.stringify(h)===JSON.stringify(i))return e}const c=y(s,`choices-${k()}.tmp`),u=E(c,"wx",384);try{J(u,`${JSON.stringify(i)}
3
+ `),v(u)}finally{w(u)}try{if(d(o,["rev-parse","--verify","HEAD"])!==r||m(o,t).planSha256!==e.planSha256)throw new Error("property-test-choices-stale");$(c,a);const h=E(s,"r");try{v(h)}finally{w(h)}}finally{p(c)&&A(c)}return e}function L(n){const{root:t,directory:o,head:s}=S(n,!1);if(!p(o))return;const r=y(o,"choices.json");if(!p(r))return;const e=g(r);if(e.sourceHead!==s)throw new Error("property-test-choices-stale");const i=m(t,e.choices);if(i.planSha256!==e.planSha256)throw new Error("property-test-choices-stale");if(e.plan&&JSON.stringify(e.plan)!==JSON.stringify(i))throw new Error("property-test-choices-stale");return{choices:e.choices,plan:i}}function U(n){const{root:t,directory:o,head:s}=S(n,!1),r=y(o,"choices.json");if(!p(r))throw new Error("property-test-choices-missing");const e=g(r);if(e.sourceHead!==s||!e.plan)throw new Error("property-test-choices-stale");return{root:t,head:s,choices:e.choices,plan:e.plan}}export{U as loadPropertyTestCandidatePlan,L as loadPropertyTestChoices,z as savePropertyTestChoices};
@@ -1,271 +1,7 @@
1
- /**
2
- * Root-owned, local-only preflight for the protected property-test command.
3
- * This module never emits PRD expressions or generated workflow bytes. It is
4
- * not a private-execution grant and does not accept a QA checkpoint.
5
- */
6
- import { createHash } from 'node:crypto';
7
- import { lstatSync, readFileSync, realpathSync } from 'node:fs';
8
- import { join, relative, resolve, sep } from 'node:path';
9
- const PRD_PATHS = ['prd.md', 'docs/architecture/prd.md'];
10
- const TYPES = new Set(['Range', 'Cardinality', 'SumEquality', 'Format', 'Temporal']);
11
- const AGGREGATE = /^[A-Z][A-Za-z0-9]{0,79}$/;
12
- const SHA256 = /^[a-f0-9]{64}$/;
13
- function digest(value) {
14
- return createHash('sha256').update(value).digest('hex');
15
- }
16
- function safeFile(root, path) {
17
- if (!/^[A-Za-z0-9][A-Za-z0-9._/-]{0,255}$/.test(path)
18
- || path.split('/').some((part) => part === '.' || part === '..' || part.length === 0)) {
19
- throw new Error('property-test-invalid-path');
20
- }
21
- const absolute = resolve(root, path);
22
- const rel = relative(root, absolute);
23
- if (rel.startsWith(`..${sep}`) || rel === '..' || rel === '')
24
- throw new Error('property-test-path-escape');
25
- let current = root;
26
- for (const part of path.split('/')) {
27
- current = join(current, part);
28
- const stat = lstatSync(current);
29
- if (stat.isSymbolicLink())
30
- throw new Error('property-test-symlink');
31
- }
32
- if (!lstatSync(absolute).isFile())
33
- throw new Error('property-test-not-regular');
34
- return absolute;
35
- }
36
- function slug(name) {
37
- return name.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
38
- }
39
- function tableCells(line) {
40
- const cells = [];
41
- let cell = '';
42
- for (let index = 1; index < line.length - 1; index++) {
43
- const char = line[index];
44
- if (char === '\\' && line[index + 1] === '|') {
45
- cell += '|';
46
- index++;
47
- }
48
- else if (char === '|') {
49
- cells.push(cell.trim());
50
- cell = '';
51
- }
52
- else
53
- cell += char;
54
- }
55
- cells.push(cell.trim());
56
- return cells;
57
- }
58
- function testPreimageSha256(root, path) {
59
- let current = root;
60
- for (const part of path.split('/')) {
61
- current = join(current, part);
62
- try {
63
- const stat = lstatSync(current);
64
- if (stat.isSymbolicLink())
65
- throw new Error('property-test-symlink');
66
- if (current === join(root, path) && !stat.isFile())
67
- throw new Error('property-test-invalid-destination');
68
- }
69
- catch (error) {
70
- if (error.code === 'ENOENT')
71
- return null;
72
- throw error;
73
- }
74
- }
75
- return digest(readFileSync(current));
76
- }
77
- function parseCatalog(markdown) {
78
- const lines = markdown.split(/\r?\n/);
79
- const start = lines.findIndex((line) => /^## Aggregate Invariants\s*$/.test(line));
80
- if (start < 0)
81
- throw new Error('property-test-no-invariants-section');
82
- const counts = new Map();
83
- const rows = [];
84
- let sectionAggregate;
85
- let header;
86
- for (const line of lines.slice(start + 1)) {
87
- if (/^##\s/.test(line))
88
- break;
89
- const heading = /^###\s+(.+?)\s*$/.exec(line);
90
- if (heading) {
91
- sectionAggregate = heading[1];
92
- if (!AGGREGATE.test(sectionAggregate))
93
- throw new Error('property-test-invalid-aggregate');
94
- header = undefined;
95
- continue;
96
- }
97
- if (!line.startsWith('|') || !line.endsWith('|'))
98
- continue;
99
- const cells = tableCells(line);
100
- if (cells.every((cell) => /^:?-{3,}:?$/.test(cell)))
101
- continue;
102
- if (cells.includes('Expression') && cells.includes('Type')) {
103
- header = cells;
104
- continue;
105
- }
106
- if (!header || cells.length !== header.length)
107
- throw new Error('property-test-invalid-table');
108
- const columns = header;
109
- const field = (key) => cells[columns.indexOf(key)] ?? '';
110
- const aggregate = sectionAggregate ?? field('Aggregate');
111
- if (!AGGREGATE.test(aggregate) || !field('Expression') || !TYPES.has(field('Type'))
112
- || !field('Description') || (sectionAggregate && field('Aggregate') && field('Aggregate') !== sectionAggregate)) {
113
- throw new Error('property-test-invalid-invariant');
114
- }
115
- const ordinal = (counts.get(aggregate) ?? 0) + 1;
116
- counts.set(aggregate, ordinal);
117
- rows.push(Object.freeze({ aggregate, ordinal,
118
- invariantSha256: digest(`property-test-invariant-v1\0${JSON.stringify({
119
- aggregate, ordinal, expression: field('Expression'), type: field('Type'),
120
- description: field('Description'),
121
- })}`),
122
- }));
123
- if (counts.size > 64 || [...counts.values()].reduce((sum, count) => sum + count, 0) > 1024) {
124
- throw new Error('property-test-catalog-too-large');
125
- }
126
- }
127
- if (counts.size === 0)
128
- throw new Error('property-test-no-aggregates');
129
- return { counts, rows };
130
- }
131
- /** Closed identifiers for root semantic review; the PRD text stays local. */
132
- export function propertyTestInvariantRefs(markdown) {
133
- return parseCatalog(markdown).rows;
134
- }
135
- function readCatalog(projectRoot) {
136
- const root = realpathSync(projectRoot);
137
- const matches = [];
138
- for (const path of PRD_PATHS) {
139
- let markdown;
140
- let bytes;
141
- try {
142
- bytes = readFileSync(safeFile(root, path));
143
- markdown = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
144
- }
145
- catch (error) {
146
- if (error.code === 'ENOENT')
147
- continue;
148
- throw error;
149
- }
150
- if (bytes.length > 2_000_000)
151
- throw new Error('property-test-prd-too-large');
152
- if (!/^## Aggregate Invariants\s*$/m.test(markdown))
153
- continue;
154
- matches.push({ path, sha: digest(bytes), counts: parseCatalog(markdown).counts });
155
- }
156
- if (matches.length !== 1)
157
- throw new Error(matches.length ? 'property-test-ambiguous-prd' : 'property-test-no-invariants-section');
158
- return { root, ...matches[0] };
159
- }
160
- function suggestedFiles(root, name) {
161
- const lower = slug(name);
162
- const candidates = [
163
- `src/domain/${lower}.ts`, `src/aggregates/${lower}.ts`, `src/${lower}.ts`,
164
- `src/domain/${lower.replaceAll('-', '_')}.py`, `aggregates/${lower.replaceAll('-', '_')}.py`,
165
- ];
166
- return candidates.filter((path) => {
167
- try {
168
- safeFile(root, path);
169
- return true;
170
- }
171
- catch {
172
- return false;
173
- }
174
- });
175
- }
176
- export function inspectPropertyTestCatalog(projectRoot) {
177
- const source = readCatalog(projectRoot);
178
- const aggregates = [...source.counts].sort(([a], [b]) => a.localeCompare(b)).map(([name, invariantCount]) => ({
179
- name, invariantCount, suggestedFiles: suggestedFiles(source.root, name),
180
- }));
181
- return {
182
- schemaVersion: 1, prdPath: source.path, prdSha256: source.sha,
183
- aggregates, invariantCount: aggregates.reduce((sum, item) => sum + item.invariantCount, 0),
184
- publicSafe: true,
185
- };
186
- }
187
- export function confirmPropertyTestPlan(projectRoot, choices) {
188
- const source = readCatalog(projectRoot);
189
- if (!choices || !SHA256.test(choices.prdSha256) || choices.prdSha256 !== source.sha
190
- || !['typescript', 'python'].includes(choices.language)
191
- || ![100, 1000, 10000].includes(choices.numRuns)
192
- || !choices.mappings || typeof choices.mappings !== 'object' || Array.isArray(choices.mappings)
193
- || Object.keys(choices).sort().join('\0') !== ['language', 'mappings', 'numRuns', 'prdSha256'].join('\0')
194
- || Object.keys(choices.mappings).sort().join('\0') !== [...source.counts.keys()].sort().join('\0')) {
195
- throw new Error('property-test-choices-mismatch');
196
- }
197
- const paths = new Set();
198
- const aggregates = [...source.counts].sort(([a], [b]) => a.localeCompare(b)).map(([name, invariantCount]) => {
199
- const implementationFile = choices.mappings[name];
200
- if (typeof implementationFile !== 'string'
201
- || !/^(?:src|aggregates)\/[A-Za-z0-9_./-]+\.(?:ts|py)$/.test(implementationFile)
202
- || (choices.language === 'typescript' ? !implementationFile.endsWith('.ts') : !implementationFile.endsWith('.py'))) {
203
- throw new Error('property-test-invalid-mapping');
204
- }
205
- const implementationSha256 = digest(readFileSync(safeFile(source.root, implementationFile)));
206
- const stem = slug(name);
207
- const testFile = choices.language === 'typescript'
208
- ? `tests/property/${stem}.property.test.ts`
209
- : `tests/property/test_${stem.replaceAll('-', '_')}_properties.py`;
210
- if (paths.has(testFile))
211
- throw new Error('property-test-write-collision');
212
- const testPreimage = testPreimageSha256(source.root, testFile);
213
- paths.add(testFile);
214
- return { name, invariantCount, implementationFile, implementationSha256,
215
- testFile, testPreimageSha256: testPreimage };
216
- });
217
- const writeSet = [...paths].sort();
218
- const basis = { schemaVersion: 1, prdPath: source.path, prdSha256: source.sha,
219
- language: choices.language, numRuns: choices.numRuns, aggregates, writeSet };
220
- return { ...basis, planSha256: digest(JSON.stringify(basis)), checkpointAccepted: false, publicSafe: true };
221
- }
222
- export function propertyTestExecutionInput(plan) {
223
- if (!PRD_PATHS.includes(plan.prdPath)
224
- || !SHA256.test(plan.planSha256) || !SHA256.test(plan.prdSha256)
225
- || plan.aggregates.length < 1 || plan.aggregates.length > 64
226
- || plan.writeSet.length !== plan.aggregates.length
227
- || plan.aggregates.reduce((sum, item) => sum + item.invariantCount, 0) > 1024) {
228
- throw new Error('property-test-plan-invalid');
229
- }
230
- return {
231
- schemaVersion: 1,
232
- prdPath: plan.prdPath,
233
- prdSha256: plan.prdSha256,
234
- planSha256: plan.planSha256,
235
- pathsSha256: digest(JSON.stringify(plan.writeSet)),
236
- aggregateCount: plan.aggregates.length,
237
- invariantCount: plan.aggregates.reduce((sum, item) => sum + item.invariantCount, 0),
238
- pathCount: plan.writeSet.length,
239
- language: plan.language,
240
- numRuns: plan.numRuns,
241
- publicSafe: true,
242
- };
243
- }
244
- /** Build root-owned PRD configuration bytes; no invariant expression leaves the local PRD. */
245
- export function renderPropertyTestConfiguration(currentPrd, plan, generatedAt) {
246
- if (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(generatedAt)
247
- || !Number.isFinite(Date.parse(generatedAt))
248
- || !SHA256.test(plan.planSha256)
249
- || digest(currentPrd) !== plan.prdSha256
250
- || /^## Property Test Configuration\s*$/m.test(currentPrd)
251
- || currentPrd.includes('\0')) {
252
- throw new Error('property-test-configuration-conflict');
253
- }
254
- const newline = currentPrd.includes('\r\n') ? '\r\n' : '\n';
255
- if (newline === '\r\n' && currentPrd.replaceAll('\r\n', '').includes('\n')) {
256
- throw new Error('property-test-configuration-conflict');
257
- }
258
- const lines = [
259
- '## Property Test Configuration', '',
260
- `- Language: ${plan.language}`,
261
- `- numRuns: ${plan.numRuns}`,
262
- `- Generated at: ${generatedAt}`,
263
- '- Step: property-test (P112.03)',
264
- `- Confirmed plan SHA-256: ${plan.planSha256}`,
265
- '', '| Aggregate | Implementation File | Invariants |',
266
- '|---|---|---|',
267
- ...plan.aggregates.map((item) => `| ${item.name} | ${item.implementationFile} | ${item.invariantCount} |`),
268
- '',
269
- ];
270
- return `${currentPrd.replace(/(?:\r?\n)*$/, '')}${newline}${newline}${lines.join(newline)}`;
271
- }
1
+ import{createHash as x}from"node:crypto";import{lstatSync as m,readFileSync as w,realpathSync as P}from"node:fs";import{join as S,relative as F,resolve as R,sep as j}from"node:path";const E=["prd.md","docs/architecture/prd.md"],k=new Set(["Range","Cardinality","SumEquality","Format","Temporal"]),v=/^[A-Z][A-Za-z0-9]{0,79}$/,d=/^[a-f0-9]{64}$/;function l(t){return x("sha256").update(t).digest("hex")}function $(t,e){if(!/^[A-Za-z0-9][A-Za-z0-9._/-]{0,255}$/.test(e)||e.split("/").some(o=>o==="."||o===".."||o.length===0))throw new Error("property-test-invalid-path");const r=R(t,e),n=F(t,r);if(n.startsWith(`..${j}`)||n===".."||n==="")throw new Error("property-test-path-escape");let s=t;for(const o of e.split("/"))if(s=S(s,o),m(s).isSymbolicLink())throw new Error("property-test-symlink");if(!m(r).isFile())throw new Error("property-test-not-regular");return r}function A(t){return t.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase()}function _(t){const e=[];let r="";for(let n=1;n<t.length-1;n++){const s=t[n];s==="\\"&&t[n+1]==="|"?(r+="|",n++):s==="|"?(e.push(r.trim()),r=""):r+=s}return e.push(r.trim()),e}function O(t,e){let r=t;for(const n of e.split("/")){r=S(r,n);try{const s=m(r);if(s.isSymbolicLink())throw new Error("property-test-symlink");if(r===S(t,e)&&!s.isFile())throw new Error("property-test-invalid-destination")}catch(s){if(s.code==="ENOENT")return null;throw s}}return l(w(r))}function C(t){const e=t.split(/\r?\n/),r=e.findIndex(a=>/^## Aggregate Invariants\s*$/.test(a));if(r<0)throw new Error("property-test-no-invariants-section");const n=new Map,s=[];let o,p;for(const a of e.slice(r+1)){if(/^##\s/.test(a))break;const u=/^###\s+(.+?)\s*$/.exec(a);if(u){if(o=u[1],!v.test(o))throw new Error("property-test-invalid-aggregate");p=void 0;continue}if(!a.startsWith("|")||!a.endsWith("|"))continue;const i=_(a);if(i.every(h=>/^:?-{3,}:?$/.test(h)))continue;if(i.includes("Expression")&&i.includes("Type")){p=i;continue}if(!p||i.length!==p.length)throw new Error("property-test-invalid-table");const y=p,g=h=>i[y.indexOf(h)]??"",c=o??g("Aggregate");if(!v.test(c)||!g("Expression")||!k.has(g("Type"))||!g("Description")||o&&g("Aggregate")&&g("Aggregate")!==o)throw new Error("property-test-invalid-invariant");const f=(n.get(c)??0)+1;if(n.set(c,f),s.push(Object.freeze({aggregate:c,ordinal:f,invariantSha256:l(`property-test-invariant-v1\0${JSON.stringify({aggregate:c,ordinal:f,expression:g("Expression"),type:g("Type"),description:g("Description")})}`)})),n.size>64||[...n.values()].reduce((h,b)=>h+b,0)>1024)throw new Error("property-test-catalog-too-large")}if(n.size===0)throw new Error("property-test-no-aggregates");return{counts:n,rows:s}}function D(t){return C(t).rows}function T(t){const e=P(t),r=[];for(const n of E){let s,o;try{o=w($(e,n)),s=new TextDecoder("utf-8",{fatal:!0}).decode(o)}catch(p){if(p.code==="ENOENT")continue;throw p}if(o.length>2e6)throw new Error("property-test-prd-too-large");/^## Aggregate Invariants\s*$/m.test(s)&&r.push({path:n,sha:l(o),counts:C(s).counts})}if(r.length!==1)throw new Error(r.length?"property-test-ambiguous-prd":"property-test-no-invariants-section");return{root:e,...r[0]}}function z(t,e){const r=A(e);return[`src/domain/${r}.ts`,`src/aggregates/${r}.ts`,`src/${r}.ts`,`src/domain/${r.replaceAll("-","_")}.py`,`aggregates/${r.replaceAll("-","_")}.py`].filter(s=>{try{return $(t,s),!0}catch{return!1}})}function W(t){const e=T(t),r=[...e.counts].sort(([n],[s])=>n.localeCompare(s)).map(([n,s])=>({name:n,invariantCount:s,suggestedFiles:z(e.root,n)}));return{schemaVersion:1,prdPath:e.path,prdSha256:e.sha,aggregates:r,invariantCount:r.reduce((n,s)=>n+s.invariantCount,0),publicSafe:!0}}function G(t,e){const r=T(t);if(!e||!d.test(e.prdSha256)||e.prdSha256!==r.sha||!["typescript","python"].includes(e.language)||![100,1e3,1e4].includes(e.numRuns)||!e.mappings||typeof e.mappings!="object"||Array.isArray(e.mappings)||Object.keys(e).sort().join("\0")!==["language","mappings","numRuns","prdSha256"].join("\0")||Object.keys(e.mappings).sort().join("\0")!==[...r.counts.keys()].sort().join("\0"))throw new Error("property-test-choices-mismatch");const n=new Set,s=[...r.counts].sort(([a],[u])=>a.localeCompare(u)).map(([a,u])=>{const i=e.mappings[a];if(typeof i!="string"||!/^(?:src|aggregates)\/[A-Za-z0-9_./-]+\.(?:ts|py)$/.test(i)||(e.language==="typescript"?!i.endsWith(".ts"):!i.endsWith(".py")))throw new Error("property-test-invalid-mapping");const y=l(w($(r.root,i))),g=A(a),c=e.language==="typescript"?`tests/property/${g}.property.test.ts`:`tests/property/test_${g.replaceAll("-","_")}_properties.py`;if(n.has(c))throw new Error("property-test-write-collision");const f=O(r.root,c);return n.add(c),{name:a,invariantCount:u,implementationFile:i,implementationSha256:y,testFile:c,testPreimageSha256:f}}),o=[...n].sort(),p={schemaVersion:1,prdPath:r.path,prdSha256:r.sha,language:e.language,numRuns:e.numRuns,aggregates:s,writeSet:o};return{...p,planSha256:l(JSON.stringify(p)),checkpointAccepted:!1,publicSafe:!0}}function H(t){if(!E.includes(t.prdPath)||!d.test(t.planSha256)||!d.test(t.prdSha256)||t.aggregates.length<1||t.aggregates.length>64||t.writeSet.length!==t.aggregates.length||t.aggregates.reduce((e,r)=>e+r.invariantCount,0)>1024)throw new Error("property-test-plan-invalid");return{schemaVersion:1,prdPath:t.prdPath,prdSha256:t.prdSha256,planSha256:t.planSha256,pathsSha256:l(JSON.stringify(t.writeSet)),aggregateCount:t.aggregates.length,invariantCount:t.aggregates.reduce((e,r)=>e+r.invariantCount,0),pathCount:t.writeSet.length,language:t.language,numRuns:t.numRuns,publicSafe:!0}}function L(t,e,r){if(!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(r)||!Number.isFinite(Date.parse(r))||!d.test(e.planSha256)||l(t)!==e.prdSha256||/^## Property Test Configuration\s*$/m.test(t)||t.includes("\0"))throw new Error("property-test-configuration-conflict");const n=t.includes(`\r
2
+ `)?`\r
3
+ `:`
4
+ `;if(n===`\r
5
+ `&&t.replaceAll(`\r
6
+ `,"").includes(`
7
+ `))throw new Error("property-test-configuration-conflict");const s=["## Property Test Configuration","",`- Language: ${e.language}`,`- numRuns: ${e.numRuns}`,`- Generated at: ${r}`,"- Step: property-test (P112.03)",`- Confirmed plan SHA-256: ${e.planSha256}`,"","| Aggregate | Implementation File | Invariants |","|---|---|---|",...e.aggregates.map(o=>`| ${o.name} | ${o.implementationFile} | ${o.invariantCount} |`),""];return`${t.replace(/(?:\r?\n)*$/,"")}${n}${n}${s.join(n)}`}export{G as confirmPropertyTestPlan,W as inspectPropertyTestCatalog,H as propertyTestExecutionInput,D as propertyTestInvariantRefs,L as renderPropertyTestConfiguration};