@ornexus/neocortex 4.60.73 → 4.60.75
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 +1216 -1210
- package/install.ps1 +1 -1
- package/install.sh +1 -1
- package/package.json +2 -2
- 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.d.ts +27 -0
- 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 +5 -358
- 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 +38 -1
- package/packages/client/dist/commands/invoke.js +143 -7200
- 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/host-auth-broker.d.ts +31 -0
- package/packages/client/dist/config/host-auth-broker.js +1 -0
- package/packages/client/dist/config/host-auth-reference.d.ts +32 -0
- package/packages/client/dist/config/host-auth-reference.js +1 -0
- 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.d.ts +11 -0
- 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 -2612
- package/packages/client/dist/effects/invoke-effect-gateways.js +1 -62
- package/packages/client/dist/errors/error-messages.d.ts +3 -0
- 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.d.ts +11 -1
- package/packages/client/dist/git/direct-yolo-checkpoint.js +4 -1234
- package/packages/client/dist/git/epic-operational-state.js +2 -672
- package/packages/client/dist/git/git-lifecycle-step-ids.js +1 -14
- package/packages/client/dist/git/git-lifecycle-store.d.ts +23 -0
- package/packages/client/dist/git/git-lifecycle-store.js +10 -1981
- 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.d.ts +2 -0
- package/packages/client/dist/index.js +1 -47
- package/packages/client/dist/invocation/invoke-input.js +1 -24
- package/packages/client/dist/license/host-auth-recovery.d.ts +158 -0
- package/packages/client/dist/license/host-auth-recovery.js +1 -0
- package/packages/client/dist/license/index.js +1 -5
- package/packages/client/dist/license/license-client.d.ts +10 -1
- 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/epic-lifecycle-backfill.js +3 -1060
- 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 -393
- 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/registry/registry-reconciliation.js +1 -234
- 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/epic-background-dispatcher.d.ts +61 -0
- package/packages/client/dist/runner/epic-background-dispatcher.js +1 -0
- package/packages/client/dist/runner/headless/adapters.js +1 -49
- package/packages/client/dist/runner/headless/background-root.d.ts +65 -0
- package/packages/client/dist/runner/headless/background-root.js +1 -0
- 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.d.ts +1 -1
- package/packages/client/dist/runner/headless/epic-merge-candidate-qa.js +2 -110
- package/packages/client/dist/runner/headless/epic-terminal-git-cli-adapter.d.ts +2 -0
- package/packages/client/dist/runner/headless/epic-terminal-git-cli-adapter.js +10 -1236
- package/packages/client/dist/runner/headless/epic-terminal-git-runner.d.ts +27 -2
- 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-environment-cleanup.d.ts +95 -0
- package/packages/client/dist/runner/headless/managed-environment-cleanup.js +1 -0
- 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.d.ts +1 -0
- 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.d.ts +7 -0
- package/packages/client/dist/state/project-state-snapshot.js +1 -1731
- 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.d.ts +21 -1
- 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 -184
- 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 -1920
- package/packages/shared/dist/invocation/explicit-intent-recovery.d.ts +83 -0
- package/packages/shared/dist/invocation/explicit-intent-recovery.js +187 -0
- package/packages/shared/dist/invocation/index.d.ts +7 -0
- package/packages/shared/dist/invocation/index.js +22 -0
- package/packages/shared/dist/yolo/change-evidence.js +1 -1
- package/packages/shared/dist/yoloop/epic-terminal-git.js +144 -2
- 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 +24 -21
- package/targets-stubs/codex/README.md +6 -1
- package/targets-stubs/codex/neocortex.toml +7 -2
- package/targets-stubs/cursor/agent.md +2 -2
- package/targets-stubs/gemini-cli/agent.md +2 -2
- package/targets-stubs/opencode/neocortex-root.md +16 -1
- package/targets-stubs/opencode/neocortex.md +6 -0
- 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};
|