@ornexus/neocortex 4.60.68 → 4.60.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/release-source.json +1 -1
- package/dist/sbom.cdx.json +1194 -1194
- package/install.ps1 +1 -1
- package/install.sh +1 -1
- package/package.json +3 -3
- package/packages/client/dist/adapters/adapter-registry.js +1 -108
- package/packages/client/dist/adapters/antigravity-adapter.js +2 -77
- package/packages/client/dist/adapters/claude-code-adapter.js +3 -79
- package/packages/client/dist/adapters/codex-adapter.js +2 -80
- package/packages/client/dist/adapters/cursor-adapter.js +4 -115
- package/packages/client/dist/adapters/gemini-adapter.js +2 -71
- package/packages/client/dist/adapters/grok-adapter.js +2 -42
- package/packages/client/dist/adapters/index.js +1 -22
- package/packages/client/dist/adapters/platform-detector.js +1 -212
- package/packages/client/dist/adapters/target-adapter.js +0 -12
- package/packages/client/dist/adapters/vscode-adapter.js +2 -72
- package/packages/client/dist/agent/refresh-stubs.js +2 -417
- package/packages/client/dist/agent/update-agent-yaml.js +1 -102
- package/packages/client/dist/agent/update-description.js +1 -251
- package/packages/client/dist/cache/crypto-utils.js +1 -78
- package/packages/client/dist/cache/encrypted-cache.js +1 -108
- package/packages/client/dist/cache/in-memory-asset-cache.js +1 -73
- package/packages/client/dist/cache/index.js +1 -13
- package/packages/client/dist/cache/protected-pi-boundary.js +1 -44
- package/packages/client/dist/checkpoint/checkpoint-client-reader.js +2 -127
- package/packages/client/dist/checkpoint/index.js +1 -11
- package/packages/client/dist/checkpoint/shared-checkpoint-types.js +1 -32
- package/packages/client/dist/cli.js +3 -290
- package/packages/client/dist/commands/activate.js +8 -394
- package/packages/client/dist/commands/architecture-answers.js +7 -274
- package/packages/client/dist/commands/architecture-memory-terminal.js +4 -253
- package/packages/client/dist/commands/architecture-non-ui-continuation.js +1 -100
- package/packages/client/dist/commands/architecture-root-binding.js +1 -51
- package/packages/client/dist/commands/cache-status.js +2 -112
- package/packages/client/dist/commands/config.js +1 -88
- package/packages/client/dist/commands/doctor.js +2 -635
- package/packages/client/dist/commands/invoke.d.ts +6 -0
- package/packages/client/dist/commands/invoke.js +142 -7140
- package/packages/client/dist/commands/legacy-epic-terminal.js +1 -71
- package/packages/client/dist/commands/legacy-story-branch.js +1 -96
- package/packages/client/dist/commands/legacy-story-pr.js +1 -276
- package/packages/client/dist/commands/mutation-qa-checkpoint.js +5 -285
- package/packages/client/dist/commands/mutation-qa-gate.js +1 -54
- package/packages/client/dist/commands/mutation-qa-git.js +1 -41
- package/packages/client/dist/commands/mutation-qa-policy.js +1 -102
- package/packages/client/dist/commands/mutation-qa-run.js +3 -219
- package/packages/client/dist/commands/mutation-qa-verdict.js +1 -149
- package/packages/client/dist/commands/mutation-qa.js +1 -220
- package/packages/client/dist/commands/native-agent.js +2 -136
- package/packages/client/dist/commands/planning-diagnosis.js +1 -43
- package/packages/client/dist/commands/planning-generation.js +1 -53
- package/packages/client/dist/commands/planning-memory.js +1 -40
- package/packages/client/dist/commands/planning-research.js +1 -42
- package/packages/client/dist/commands/private-session.js +1 -75
- package/packages/client/dist/commands/property-qa.js +1 -139
- package/packages/client/dist/commands/qa-control.js +1 -16
- package/packages/client/dist/commands/qa-scan-browser-launch.js +1 -55
- package/packages/client/dist/commands/qa-scan-browser-route.js +1 -28
- package/packages/client/dist/commands/qa-scan-capture.js +5 -663
- package/packages/client/dist/commands/qa-scan-discovery.js +3 -376
- package/packages/client/dist/commands/qa-scan-review.js +5 -826
- package/packages/client/dist/commands/qa-synthetic-backend-capture.js +1 -266
- package/packages/client/dist/commands/qa-synthetic-backend-review.js +1 -297
- package/packages/client/dist/commands/qa-synthetic-backend.js +2 -340
- package/packages/client/dist/commands/qa-synthetic-broker-port.js +1 -85
- package/packages/client/dist/commands/qa-synthetic-browser.js +1 -39
- package/packages/client/dist/commands/refactor-characterization.js +21 -671
- package/packages/client/dist/commands/refactor-commit.js +13 -399
- package/packages/client/dist/commands/refactor-control.js +3 -142
- package/packages/client/dist/commands/refactor-cpp-assertions.js +3 -93
- package/packages/client/dist/commands/refactor-review.js +5 -283
- package/packages/client/dist/commands/refactor-root.js +7 -588
- package/packages/client/dist/commands/refresh-memory.js +3 -156
- package/packages/client/dist/commands/shared-architecture-non-ui.js +1 -24
- package/packages/client/dist/commands/story-checkpoint.js +1 -171
- package/packages/client/dist/config/profile-directory.js +1 -13
- package/packages/client/dist/config/resolver-selection.js +1 -278
- package/packages/client/dist/config/secure-config.js +12 -281
- package/packages/client/dist/config.js +2 -223
- package/packages/client/dist/constants.js +1 -25
- package/packages/client/dist/context/context-collector.js +2 -222
- package/packages/client/dist/context/context-sanitizer.js +1 -145
- package/packages/client/dist/continuity/autonomous-governance.js +1 -285
- package/packages/client/dist/continuity/continuity-client-state-store.js +1 -1587
- package/packages/client/dist/continuity/invoke-hooks.js +1 -129
- package/packages/client/dist/continuity/migrations/001-initial-schema.js +2 -12
- package/packages/client/dist/continuity/migrations/002-processing-output-summary-indexes.js +2 -12
- package/packages/client/dist/continuity/migrations/003-headless-execution-attempts.js +2 -12
- package/packages/client/dist/continuity/migrations/004-outbox-delivery-state.js +2 -12
- package/packages/client/dist/continuity/migrations/005-bounded-headless-queue.js +2 -12
- package/packages/client/dist/continuity/migrations/006-native-agent-dispatches.js +2 -10
- package/packages/client/dist/continuity/native-agent-store.js +1 -69
- package/packages/client/dist/continuity/native-agent-types.js +1 -90
- package/packages/client/dist/continuity/outbox.js +1 -117
- package/packages/client/dist/continuity/runner-bootstrap-policy.js +1 -172
- package/packages/client/dist/continuity/sqlite-store.js +81 -2536
- package/packages/client/dist/effects/invoke-effect-gateways.js +1 -62
- package/packages/client/dist/errors/error-messages.js +2 -228
- package/packages/client/dist/errors/frontmatter-diagnostics.js +1 -125
- package/packages/client/dist/evidence/collector.js +1 -347
- package/packages/client/dist/evidence/index.js +1 -1
- package/packages/client/dist/evidence/types.js +0 -2
- package/packages/client/dist/fs/atomic-state-replace.js +1 -152
- package/packages/client/dist/generated/command-registry.generated.js +1 -4076
- package/packages/client/dist/generated/public-projections.generated.js +1 -149
- package/packages/client/dist/git/direct-yolo-c01-retry.js +4 -138
- package/packages/client/dist/git/direct-yolo-checkpoint.js +4 -1234
- package/packages/client/dist/git/git-lifecycle-step-ids.js +1 -14
- package/packages/client/dist/git/git-lifecycle-store.js +9 -1938
- package/packages/client/dist/git/legacy-story-branch-checkpoint.js +9 -259
- package/packages/client/dist/git/legacy-story-branch-setup.js +4 -348
- package/packages/client/dist/git/legacy-story-pr.js +70 -3538
- package/packages/client/dist/git/secure-filesystem-boundary.js +5 -182
- package/packages/client/dist/graph-retrieval/pre-command-hook.js +1 -284
- package/packages/client/dist/graph-retrieval/shared-graph-retrieval-contract.js +1 -237
- package/packages/client/dist/i18n/first-run.js +2 -60
- package/packages/client/dist/index.js +1 -47
- package/packages/client/dist/invocation/invoke-input.js +1 -24
- package/packages/client/dist/license/index.js +1 -5
- package/packages/client/dist/license/license-client.js +1 -304
- package/packages/client/dist/machine/fingerprint.js +2 -163
- package/packages/client/dist/machine/index.js +1 -5
- package/packages/client/dist/memory/project-memory-path-scope.js +1 -62
- package/packages/client/dist/memory/project-memory-writer.js +40 -1007
- package/packages/client/dist/memory/shared-project-memory-types.js +2 -253
- package/packages/client/dist/migrations/project-identity-capability.js +1 -50
- package/packages/client/dist/migrations/project-identity.js +7 -493
- package/packages/client/dist/migrations/state-json.js +4 -344
- package/packages/client/dist/policy/architecture-policy.js +2 -184
- package/packages/client/dist/policy/index.js +1 -7
- package/packages/client/dist/policy/shared-policy-types.js +0 -38
- package/packages/client/dist/policy/subagent-approval-policy.js +1 -55
- package/packages/client/dist/private-execution/clean-baseline.js +2 -279
- package/packages/client/dist/private-execution/codex-executor.js +4 -574
- package/packages/client/dist/private-execution/codex-host-runtime.js +1 -137
- package/packages/client/dist/private-execution/codex-lifecycle-guard.js +1 -97
- package/packages/client/dist/private-execution/codex-scoped-permissions.js +1 -128
- package/packages/client/dist/private-execution/delivery.js +13 -410
- package/packages/client/dist/private-execution/diagnosis-root-review.js +2 -294
- package/packages/client/dist/private-execution/frozen-handoff.js +6 -19
- package/packages/client/dist/private-execution/implementation-validation.js +2 -106
- package/packages/client/dist/private-execution/isolated-admission.js +23 -1432
- package/packages/client/dist/private-execution/isolated-session.js +5 -725
- package/packages/client/dist/private-execution/isolated-workspace.js +1 -122
- package/packages/client/dist/private-execution/pact-consumer-runtime.js +3 -409
- package/packages/client/dist/private-execution/pact-test-source.js +1 -258
- package/packages/client/dist/private-execution/pact-toolchain.js +3 -290
- package/packages/client/dist/private-execution/pact-write-set.js +1 -172
- package/packages/client/dist/private-execution/planning-diagnosis-checkpoint.js +5 -406
- package/packages/client/dist/private-execution/planning-generation-checkpoint.js +5 -462
- package/packages/client/dist/private-execution/planning-memory-checkpoint.js +3 -139
- package/packages/client/dist/private-execution/planning-research-checkpoint.js +4 -325
- package/packages/client/dist/private-execution/private-report-store.js +1 -42
- package/packages/client/dist/private-execution/property-test-candidate.js +1 -103
- package/packages/client/dist/private-execution/property-test-choices.js +3 -147
- package/packages/client/dist/private-execution/property-test-plan.js +7 -271
- package/packages/client/dist/private-execution/property-test-review.js +4 -258
- package/packages/client/dist/private-execution/property-test-runtime.js +9 -372
- package/packages/client/dist/private-execution/property-test-source.js +1 -139
- package/packages/client/dist/private-execution/root-checkpoint.js +1 -80
- package/packages/client/dist/private-execution/root-memory-git-evidence.js +3 -135
- package/packages/client/dist/private-execution/root-memory-recovery.js +1 -93
- package/packages/client/dist/private-execution/root-memory-request.js +1 -29
- package/packages/client/dist/private-execution/root-memory-scope.js +1 -178
- package/packages/client/dist/private-execution/root-memory-writer.js +1 -102
- package/packages/client/dist/private-execution/session-lease.js +1 -63
- package/packages/client/dist/private-execution/shared-private-admission-types.js +1 -50
- package/packages/client/dist/private-execution/shared-project-validation.js +1 -66
- package/packages/client/dist/private-execution/shared-root-memory-types.js +1 -61
- package/packages/client/dist/private-execution/ux-source-validation.js +1 -372
- package/packages/client/dist/private-execution/ux-write-set.js +2 -191
- package/packages/client/dist/resilience/circuit-breaker.js +1 -170
- package/packages/client/dist/resilience/degradation-manager.js +1 -164
- package/packages/client/dist/resilience/freshness-indicator.js +1 -100
- package/packages/client/dist/resilience/index.js +1 -8
- package/packages/client/dist/resilience/recovery-detector.js +1 -74
- package/packages/client/dist/resolvers/asset-resolver.js +0 -13
- package/packages/client/dist/resolvers/local-resolver.js +8 -255
- package/packages/client/dist/resolvers/remote-resolver.js +1 -233
- package/packages/client/dist/runner/binary.js +1 -404
- package/packages/client/dist/runner/cli.js +17 -1571
- package/packages/client/dist/runner/headless/adapters.js +1 -49
- package/packages/client/dist/runner/headless/claude-code-adapter.js +1 -79
- package/packages/client/dist/runner/headless/continuous-story-workspace.js +11 -1628
- package/packages/client/dist/runner/headless/epic-git-reconciliation-inventory.js +1 -69
- package/packages/client/dist/runner/headless/epic-merge-candidate-qa.js +2 -110
- package/packages/client/dist/runner/headless/epic-terminal-git-cli-adapter.js +8 -1236
- package/packages/client/dist/runner/headless/epic-terminal-git-runner.js +2 -318
- package/packages/client/dist/runner/headless/fake-adapter.js +1 -62
- package/packages/client/dist/runner/headless/managed-profile.js +1 -132
- package/packages/client/dist/runner/headless/opencode-adapter.js +2 -145
- package/packages/client/dist/runner/headless/prepared-story-bundle.js +1 -43
- package/packages/client/dist/runner/headless/resolver.js +1 -231
- package/packages/client/dist/runner/headless/run-yolo.js +1 -885
- package/packages/client/dist/runner/headless/run-yoloop.js +2 -2574
- package/packages/client/dist/runner/headless/types.js +2 -574
- package/packages/client/dist/runner/local-console.js +13 -396
- package/packages/client/dist/runner/portal-sync-worker.js +1 -1487
- package/packages/client/dist/runner/project-identity.js +1 -106
- package/packages/client/dist/runner/scheduler.js +7 -995
- package/packages/client/dist/runner-cli.js +2 -18
- package/packages/client/dist/state/invocation-shape.js +1 -13
- package/packages/client/dist/state/project-state-snapshot.js +1 -1677
- package/packages/client/dist/state/state-json-repair.js +3 -785
- package/packages/client/dist/state/structural-dag-projection.js +1 -118
- package/packages/client/dist/telemetry/index.js +1 -5
- package/packages/client/dist/telemetry/offline-queue.js +1 -131
- package/packages/client/dist/tier/index.js +1 -5
- package/packages/client/dist/tier/tier-aware-client.js +1 -405
- package/packages/client/dist/types/index.js +1 -38
- package/packages/client/dist/version.js +1 -27
- package/packages/client/dist/yolo/change-evidence-collector.js +1 -126
- package/packages/client/dist/yolo/invoke-envelope-capability.js +1 -62
- package/packages/client/dist/yolo/spec-envelope-collector.js +5 -185
- package/packages/client/dist/yoloop/discovery-hook.js +2 -300
- package/packages/client/dist/yoloop/index.js +1 -8
- package/packages/client/dist/yoloop/invoke-hooks.js +12 -669
- package/packages/client/dist/yoloop/shared-discover-epics.js +1 -1007
- package/packages/client/dist/yoloop/shared-yoloop-types.js +1 -33
- package/packages/client/dist/yoloop/target-normalization.js +1 -180
- package/packages/client/dist/yoloop/yoloop-client-state-store.js +1 -1723
- package/targets-stubs/antigravity/gemini.md +1 -1
- package/targets-stubs/antigravity/skill/SKILL.md +1 -1
- package/targets-stubs/claude-code/neocortex-root.agent.yaml +1 -1
- package/targets-stubs/claude-code/neocortex-root.md +2 -2
- package/targets-stubs/claude-code/neocortex.agent.yaml +1 -1
- package/targets-stubs/claude-code/neocortex.md +2 -2
- package/targets-stubs/codex/AGENTS.md +1 -1
- package/targets-stubs/cursor/agent.md +2 -2
- package/targets-stubs/gemini-cli/agent.md +2 -2
- package/targets-stubs/opencode/neocortex-root.md +1 -1
- package/targets-stubs/vscode/neocortex.agent.md +2 -2
|
@@ -1,103 +1 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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};
|