@aiwg/cli 2026.9.19 → 2026.9.21
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/agentic/code/providers/capability-matrix.yaml +58 -6
- package/agentic/code/providers/model-capabilities.v1.json +34 -0
- package/agentic/code/providers/model-catalog.v1.json +55 -13
- package/bin/aiwg.mjs +2 -0
- package/dist/src/api/index.d.ts +1 -0
- package/dist/src/api/index.js +1 -0
- package/dist/src/artifacts/enrichment/cli.js +1 -1
- package/dist/src/artifacts/views/cli.js +1 -1
- package/dist/src/audit/operator-decision.js +10 -0
- package/dist/src/auth/credential-store.js +127 -27
- package/dist/src/catalog/builtin-models.json +272 -40
- package/dist/src/catalog/cli.js +1 -1
- package/dist/src/catalog/cli.mjs +1 -1
- package/dist/src/cli/handlers/effect.js +872 -0
- package/dist/src/cli/handlers/help.js +2 -0
- package/dist/src/cli/handlers/index.js +4 -2
- package/dist/src/cli/handlers/models.js +41 -10
- package/dist/src/cli/handlers/ralph.js +1 -1
- package/dist/src/cli/handlers/sessions.js +113 -37
- package/dist/src/cli/handlers/subcommands.js +31 -0
- package/dist/src/cli/handlers/use.js +46 -9
- package/dist/src/cli/handlers/utilities.js +10 -0
- package/dist/src/cli/handlers/workspace.js +3 -1
- package/dist/src/cli/scope-resolver.js +112 -9
- package/dist/src/cli/services/deployment-verification.js +5 -0
- package/dist/src/config/aiwg-config.js +3 -0
- package/dist/src/decision/acceptance-rollout.js +93 -0
- package/dist/src/decision/acceptance.js +175 -0
- package/dist/src/decision/adapters/jev.js +234 -13
- package/dist/src/decision/adapters/llm-subagent.js +60 -12
- package/dist/src/decision/admission.js +550 -0
- package/dist/src/decision/batch-benchmark.js +47 -0
- package/dist/src/decision/batch-receipts/accounting.js +75 -0
- package/dist/src/decision/batch-receipts/export.js +40 -0
- package/dist/src/decision/batch-receipts/index.js +9 -0
- package/dist/src/decision/batch-receipts/protection.js +40 -0
- package/dist/src/decision/batch-receipts/receipt.js +44 -0
- package/dist/src/decision/batch-receipts/result-store.js +341 -0
- package/dist/src/decision/batch-receipts/store.js +362 -0
- package/dist/src/decision/batch-receipts/types.js +2 -0
- package/dist/src/decision/batch-receipts/validate.js +127 -0
- package/dist/src/decision/batch.js +68 -0
- package/dist/src/decision/calibration/index.js +4 -0
- package/dist/src/decision/calibration/receipts.js +130 -0
- package/dist/src/decision/calibration/registry.js +258 -0
- package/dist/src/decision/calibration/types.js +2 -0
- package/dist/src/decision/compile-cache/benchmark.js +62 -0
- package/dist/src/decision/compile-cache/compatibility.js +65 -0
- package/dist/src/decision/compile-cache/file-store.js +280 -0
- package/dist/src/decision/compile-cache/identity.js +26 -0
- package/dist/src/decision/compile-cache/index.js +9 -0
- package/dist/src/decision/compile-cache/prefix.js +21 -0
- package/dist/src/decision/compile-cache/records.js +86 -0
- package/dist/src/decision/compile-cache/runtime.js +68 -0
- package/dist/src/decision/compile-cache/store.js +108 -0
- package/dist/src/decision/compile-cache/types.js +2 -0
- package/dist/src/decision/context-plan.js +234 -0
- package/dist/src/decision/context-qualification.js +60 -0
- package/dist/src/decision/ensemble/aggregate.js +215 -0
- package/dist/src/decision/ensemble/contract.js +316 -0
- package/dist/src/decision/ensemble/drift.js +51 -0
- package/dist/src/decision/ensemble/index.js +6 -0
- package/dist/src/decision/ensemble/report.js +71 -0
- package/dist/src/decision/ensemble/types.js +2 -0
- package/dist/src/decision/evaluate.js +1024 -78
- package/dist/src/decision/file-lifecycle-store.js +168 -0
- package/dist/src/decision/graph-beam.js +40 -0
- package/dist/src/decision/graph-budget.js +110 -0
- package/dist/src/decision/graph-decision-bridge.js +93 -0
- package/dist/src/decision/graph-evidence.js +123 -0
- package/dist/src/decision/graph-flow-adapter.js +56 -0
- package/dist/src/decision/graph-flow.js +105 -0
- package/dist/src/decision/graph-parallel.js +79 -0
- package/dist/src/decision/graph-patterns.js +20 -0
- package/dist/src/decision/graph-run-store.js +66 -0
- package/dist/src/decision/graph-run.js +166 -0
- package/dist/src/decision/graph-skill-bridge.js +91 -0
- package/dist/src/decision/graph-templates.js +56 -0
- package/dist/src/decision/graph.js +115 -0
- package/dist/src/decision/index.js +35 -0
- package/dist/src/decision/job-accounting.js +93 -0
- package/dist/src/decision/job-contract.js +186 -0
- package/dist/src/decision/job-effects.js +95 -0
- package/dist/src/decision/job-evaluate.js +80 -0
- package/dist/src/decision/job-gateway.js +121 -0
- package/dist/src/decision/job-http.js +194 -0
- package/dist/src/decision/job-payload-store.js +258 -0
- package/dist/src/decision/job-poll-limiter.js +92 -0
- package/dist/src/decision/job-quota.js +158 -0
- package/dist/src/decision/job-runtime.js +240 -0
- package/dist/src/decision/job-scheduler.js +26 -0
- package/dist/src/decision/job-service.js +117 -0
- package/dist/src/decision/job-store.js +264 -0
- package/dist/src/decision/job-worker.js +141 -0
- package/dist/src/decision/lifecycle.js +148 -0
- package/dist/src/decision/patterns/artifacts.js +173 -0
- package/dist/src/decision/patterns/catalog.js +108 -0
- package/dist/src/decision/patterns/durable-review.js +237 -0
- package/dist/src/decision/patterns/index.js +8 -0
- package/dist/src/decision/patterns/playground.js +401 -0
- package/dist/src/decision/patterns/recorded.js +43 -0
- package/dist/src/decision/patterns/specs.js +174 -0
- package/dist/src/decision/patterns/types.js +2 -0
- package/dist/src/decision/portable-secrets.js +53 -0
- package/dist/src/decision/projection.js +210 -0
- package/dist/src/decision/qualification/capture.js +67 -0
- package/dist/src/decision/qualification/drift.js +68 -0
- package/dist/src/decision/qualification/gate-evidence.js +172 -0
- package/dist/src/decision/qualification/gates.js +83 -0
- package/dist/src/decision/qualification/index.js +12 -0
- package/dist/src/decision/qualification/load.js +275 -0
- package/dist/src/decision/qualification/manifest.js +37 -0
- package/dist/src/decision/qualification/privacy.js +41 -0
- package/dist/src/decision/qualification/quality.js +213 -0
- package/dist/src/decision/qualification/release.js +116 -0
- package/dist/src/decision/qualification/runner.js +408 -0
- package/dist/src/decision/qualification/types.js +2 -0
- package/dist/src/decision/receipts.js +30 -7
- package/dist/src/decision/result-cache/index.js +6 -0
- package/dist/src/decision/result-cache/integrity.js +28 -0
- package/dist/src/decision/result-cache/key.js +28 -0
- package/dist/src/decision/result-cache/service.js +92 -0
- package/dist/src/decision/result-cache/store.js +327 -0
- package/dist/src/decision/result-cache/types.js +3 -0
- package/dist/src/decision/review/authorization.js +110 -0
- package/dist/src/decision/review/effect-ledger-adapter.js +321 -0
- package/dist/src/decision/review/index.js +12 -0
- package/dist/src/decision/review/migrate.js +34 -0
- package/dist/src/decision/review/operator-audit.js +59 -0
- package/dist/src/decision/review/recovery-adapters.js +252 -0
- package/dist/src/decision/review/recovery.js +33 -0
- package/dist/src/decision/review/service.js +498 -0
- package/dist/src/decision/review/store.js +219 -0
- package/dist/src/decision/review/types.js +2 -0
- package/dist/src/decision/review/validate.js +205 -0
- package/dist/src/decision/scheduler.js +115 -0
- package/dist/src/decision/telemetry/context.js +42 -0
- package/dist/src/decision/telemetry/debug-sidecar.js +152 -0
- package/dist/src/decision/telemetry/exporter.js +77 -0
- package/dist/src/decision/telemetry/file-debug-backend.js +112 -0
- package/dist/src/decision/telemetry/index.js +13 -0
- package/dist/src/decision/telemetry/mapping.js +146 -0
- package/dist/src/decision/telemetry/metrics.js +164 -0
- package/dist/src/decision/telemetry/otlp-http.js +183 -0
- package/dist/src/decision/telemetry/redaction.js +93 -0
- package/dist/src/decision/telemetry/retention.js +109 -0
- package/dist/src/decision/telemetry/runtime.js +310 -0
- package/dist/src/decision/telemetry/trace.js +158 -0
- package/dist/src/decision/telemetry/types.js +3 -0
- package/dist/src/decision/validate.js +135 -5
- package/dist/src/effects/checkpoint-sinks.js +94 -0
- package/dist/src/effects/checkpoint.js +76 -0
- package/dist/src/effects/decision-links.js +54 -0
- package/dist/src/effects/errors.js +48 -0
- package/dist/src/effects/identity.js +178 -0
- package/dist/src/effects/index.js +25 -0
- package/dist/src/effects/keyring.js +160 -0
- package/dist/src/effects/keys.js +130 -0
- package/dist/src/effects/ledger.js +435 -0
- package/dist/src/effects/lifecycle.js +110 -0
- package/dist/src/effects/lock-recovery.js +269 -0
- package/dist/src/effects/reader.js +103 -0
- package/dist/src/effects/records.js +114 -0
- package/dist/src/effects/redaction.js +51 -0
- package/dist/src/effects/schema.js +73 -0
- package/dist/src/effects/store.js +239 -0
- package/dist/src/effects/types.js +49 -0
- package/dist/src/effects/verifiers/decision.js +168 -0
- package/dist/src/effects/verifiers/file.js +106 -0
- package/dist/src/effects/verifiers/git.js +206 -0
- package/dist/src/effects/verifiers/index.js +90 -0
- package/dist/src/effects/verifiers/registry.js +148 -0
- package/dist/src/effects/verifiers/review.js +123 -0
- package/dist/src/effects/verifiers/tracker.js +299 -0
- package/dist/src/effects/verifiers/types.js +21 -0
- package/dist/src/effects/verify.js +295 -0
- package/dist/src/evidence/bundle.js +2 -0
- package/dist/src/extensions/commands/definitions.js +28 -0
- package/dist/src/extensions/manifest.js +1 -0
- package/dist/src/marketplace/provenance.js +25 -27
- package/dist/src/mcp/cli.mjs +11 -1
- package/dist/src/mcp/grok-build-config.mjs +189 -0
- package/dist/src/mcp/registry.js +21 -1
- package/dist/src/mcp/registry.mjs +20 -1
- package/dist/src/models/grok-build-models.js +356 -0
- package/dist/src/models/index.js +1 -0
- package/dist/src/models/model-capabilities.v1.json +34 -0
- package/dist/src/models/model-catalog.v1.json +55 -13
- package/dist/src/models/model-discovery.js +41 -5
- package/dist/src/models/provider-policy.js +3 -2
- package/dist/src/plugin/skill-command-translator.js +1 -0
- package/dist/src/providers/capability-matrix.yaml +58 -6
- package/dist/src/providers/grok-build-acp.js +192 -0
- package/dist/src/providers/grok-build-dispatch.js +251 -0
- package/dist/src/providers/grok-build-headless.js +170 -0
- package/dist/src/providers/muse-paths.js +105 -0
- package/dist/src/providers/provider-definitions.js +93 -7
- package/dist/src/providers/provider-definitions.mjs +21 -1
- package/dist/src/providers/provider-inventory.js +3 -0
- package/dist/src/security/artifact-attestation.js +4 -8
- package/dist/src/security/signing.js +119 -0
- package/dist/src/sessions/adapters/grok-build.js +187 -0
- package/dist/src/sessions/adapters/muse.js +412 -0
- package/dist/src/sessions/contracts.js +1 -1
- package/dist/src/sessions/grok-build-cli.js +196 -0
- package/dist/src/sessions/index.js +3 -0
- package/dist/src/sessions/workspace-discovery.js +9 -5
- package/dist/src/skills/deployer.js +33 -5
- package/dist/src/smiths/context-pipeline/claude-hook.js +2 -0
- package/dist/src/smiths/context-pipeline/context-file-safety.js +18 -0
- package/dist/src/smiths/context-pipeline/finalization.js +1 -32
- package/dist/src/smiths/context-pipeline/generator.js +12 -0
- package/dist/src/smiths/context-pipeline/index.js +1 -1
- package/dist/src/smiths/context-pipeline/managed-hook.js +2 -0
- package/dist/src/smiths/context-pipeline/muse-bridge.js +50 -0
- package/dist/src/smiths/context-pipeline/provider-policy.js +5 -0
- package/dist/src/smiths/context-pipeline/workspace-context.js +22 -16
- package/dist/src/smiths/hook-bridge/grok-build-translator.js +64 -0
- package/dist/src/smiths/hook-bridge/index.js +3 -0
- package/dist/src/smiths/hook-bridge/shim.js +2 -0
- package/dist/src/smiths/hook-bridge/types.js +5 -0
- package/dist/src/smiths/skillsmith/collision-detector.js +1 -0
- package/dist/src/storage/protected-files.js +102 -0
- package/dist/src/tracker/forge-http.js +371 -0
- package/dist/src/tracker/project-inputs.js +41 -0
- package/package.json +2 -1
- package/schemas/decision/BatchReceipt.v1.schema.json +50 -0
- package/schemas/decision/CalibrationArtifact.v1.schema.json +76 -0
- package/schemas/decision/CalibrationCompatibility.v1.schema.json +18 -0
- package/schemas/decision/CalibrationGovernanceReceipt.v1.schema.json +44 -0
- package/schemas/decision/DecisionAdmissionEvidence.v1.schema.json +43 -0
- package/schemas/decision/DecisionBinding.schema.json +309 -0
- package/schemas/decision/DecisionBinding.v1alpha2.schema.json +369 -0
- package/schemas/decision/DecisionCacheTelemetry.v1.schema.json +20 -0
- package/schemas/decision/DecisionChampionChallenger.v1.schema.json +100 -0
- package/schemas/decision/DecisionCompileCacheEntry.v1.schema.json +35 -0
- package/schemas/decision/DecisionCompileCacheIdentity.v1.schema.json +26 -0
- package/schemas/decision/DecisionCompileCacheTombstone.v1.schema.json +31 -0
- package/schemas/decision/DecisionContextEvidence.v1.schema.json +136 -0
- package/schemas/decision/DecisionDefinition.schema.json +159 -0
- package/schemas/decision/DecisionDefinition.v1alpha2.schema.json +203 -0
- package/schemas/decision/DecisionDriftResponse.v1.schema.json +55 -0
- package/schemas/decision/DecisionEnsembleAggregate.v1.schema.json +86 -0
- package/schemas/decision/DecisionEnsembleIntegrityReport.v1.schema.json +64 -0
- package/schemas/decision/DecisionEnsemblePolicy.v1.schema.json +130 -0
- package/schemas/decision/DecisionGraph.v1.schema.json +77 -0
- package/schemas/decision/DecisionJob.v1.schema.json +71 -0
- package/schemas/decision/DecisionLifecyclePolicy.v1.schema.json +52 -0
- package/schemas/decision/DecisionProjectionPolicy.v1.schema.json +65 -0
- package/schemas/decision/DecisionProviderPrefixCompatibility.v1.schema.json +45 -0
- package/schemas/decision/DecisionProviderPrefixEvidence.v1.schema.json +17 -0
- package/schemas/decision/DecisionProviderPrefixIdentity.v1.schema.json +36 -0
- package/schemas/decision/DecisionResult.schema.json +524 -0
- package/schemas/decision/DecisionResult.v1alpha2.schema.json +619 -0
- package/schemas/decision/DecisionResultCacheEntry.v1.schema.json +53 -0
- package/schemas/decision/DecisionReview.schema.json +29 -0
- package/schemas/decision/DecisionRuleset.schema.json +313 -0
- package/schemas/decision/DecisionRuleset.v1alpha2.schema.json +313 -0
- package/schemas/decision/DecisionTelemetryTrace.v1.schema.json +95 -0
- package/schemas/decision/RulesetResult.schema.json +721 -0
- package/schemas/decision/RulesetResult.v1alpha2.schema.json +891 -0
- package/schemas/security/pr-evidence-receipt.v1.schema.json +77 -0
- package/schemas/security/threat-assessment-report.v1.schema.json +71 -1
- package/tools/agents/deploy-agents.mjs +21 -6
- package/tools/agents/providers/base.mjs +7 -4
- package/tools/agents/providers/codex.mjs +1 -1
- package/tools/agents/providers/grok-build.d.mts +7 -0
- package/tools/agents/providers/grok-build.mjs +249 -9
- package/tools/agents/providers/model-role.mjs +6 -3
- package/tools/agents/providers/muse-hooks.mjs +785 -0
- package/tools/agents/providers/muse-paths.mjs +112 -0
- package/tools/agents/providers/muse.mjs +336 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Effect ledger storage under the artifact store.
|
|
3
|
+
*
|
|
4
|
+
* effects/<subsystem>/
|
|
5
|
+
* segments/<writer-id>.jsonl one hash-chained segment per writer
|
|
6
|
+
* segments/<writer-id>.pending a signed line awaiting append after its index claim
|
|
7
|
+
* index/<keyed-name>.json exclusive-create, one per effect ID (and one per outcome)
|
|
8
|
+
* checkpoints/<sequence>.json EffectCheckpoint.v1
|
|
9
|
+
* keyring.json EffectKeyring.v1
|
|
10
|
+
* index.key per-ledger key for index file names
|
|
11
|
+
* locks/ same-host directory locks
|
|
12
|
+
*
|
|
13
|
+
* The root resolves only through `projectAiwgWritePath`, which fails closed when
|
|
14
|
+
* a configured external artifact root is unavailable. Directories are 0700 and
|
|
15
|
+
* files 0600.
|
|
16
|
+
*/
|
|
17
|
+
import { randomBytes } from 'node:crypto';
|
|
18
|
+
import { lstat, open, readFile, readdir, rename, rm } from 'node:fs/promises';
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
import { acquireDirectoryLock } from '../artifacts/prebuilt-build-lock.js';
|
|
21
|
+
import { projectAiwgWritePath } from '../config/project-artifacts.js';
|
|
22
|
+
import { canonicalJson } from '../security/artifact-trust.js';
|
|
23
|
+
import { ensurePrivateDirectory, exists, keyedName, publishExclusive, syncDirectory, } from '../storage/protected-files.js';
|
|
24
|
+
import { EffectLedgerError, integrityError } from './errors.js';
|
|
25
|
+
import { assertWriterId, SHA256_DIGEST_PATTERN, WRITER_ID_PATTERN } from './identity.js';
|
|
26
|
+
import { INDEX_SCHEMA_VERSION } from './types.js';
|
|
27
|
+
/** Resolve the ledger root for one subsystem. Throws exit 7 when the artifact root is unavailable. */
|
|
28
|
+
export function resolveLedgerPaths(projectDir, subsystem) {
|
|
29
|
+
let root;
|
|
30
|
+
try {
|
|
31
|
+
root = projectAiwgWritePath(projectDir, 'effects', subsystem);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
throw new EffectLedgerError('artifact-root-unavailable', `${error.message} (effect ledger)`, 'artifact-root-unavailable');
|
|
35
|
+
}
|
|
36
|
+
return {
|
|
37
|
+
root,
|
|
38
|
+
segments: join(root, 'segments'),
|
|
39
|
+
index: join(root, 'index'),
|
|
40
|
+
checkpoints: join(root, 'checkpoints'),
|
|
41
|
+
locks: join(root, 'locks'),
|
|
42
|
+
keyring: join(root, 'keyring.json'),
|
|
43
|
+
indexKey: join(root, 'index.key'),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const PERMISSIONS_MESSAGE = 'Effect ledger directories must be owner-only (0700)';
|
|
47
|
+
const permissionError = (message) => new EffectLedgerError('integrity', message ?? PERMISSIONS_MESSAGE, 'ledger-permissions');
|
|
48
|
+
export async function ensureLedgerDirectories(paths) {
|
|
49
|
+
for (const directory of [paths.root, paths.segments, paths.index, paths.checkpoints, paths.locks]) {
|
|
50
|
+
await ensurePrivateDirectory(directory, PERMISSIONS_MESSAGE, permissionError);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export const segmentFile = (paths, writer) => join(paths.segments, `${writer}.jsonl`);
|
|
54
|
+
const pendingFile = (paths, writer) => join(paths.segments, `${writer}.pending`);
|
|
55
|
+
/** Serialize work for one lock name across processes on this host. */
|
|
56
|
+
export async function withLedgerLock(paths, name, timeoutMs, work) {
|
|
57
|
+
let release;
|
|
58
|
+
try {
|
|
59
|
+
release = await acquireDirectoryLock(join(paths.locks, `${name}.lock`), { timeoutMs, pollMs: 20 });
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
throw new EffectLedgerError('lock-timeout', 'Timed out waiting for the effect ledger lock', 'lock-timeout');
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
return await work();
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
await release();
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** Writers with a segment file, sorted by writer ID. */
|
|
72
|
+
export async function listWriters(paths) {
|
|
73
|
+
let names;
|
|
74
|
+
try {
|
|
75
|
+
names = await readdir(paths.segments);
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (error.code === 'ENOENT')
|
|
79
|
+
return [];
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
return names.filter(name => name.endsWith('.jsonl')).map(name => name.slice(0, -'.jsonl'.length))
|
|
83
|
+
.filter(writer => WRITER_ID_PATTERN.test(writer)).sort();
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Raw lines of one writer's segment, or null when the segment does not exist.
|
|
87
|
+
* `torn` is true when the file does not end with a newline.
|
|
88
|
+
*/
|
|
89
|
+
export async function readSegment(paths, writer) {
|
|
90
|
+
let raw;
|
|
91
|
+
try {
|
|
92
|
+
raw = await readFile(segmentFile(paths, writer), 'utf8');
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (error.code === 'ENOENT')
|
|
96
|
+
return null;
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
if (raw === '')
|
|
100
|
+
return { lines: [], torn: false };
|
|
101
|
+
const torn = !raw.endsWith('\n');
|
|
102
|
+
const lines = raw.split('\n');
|
|
103
|
+
if (!torn)
|
|
104
|
+
lines.pop();
|
|
105
|
+
return { lines, torn };
|
|
106
|
+
}
|
|
107
|
+
/** Append one serialized line and fsync. The caller holds the writer lock. */
|
|
108
|
+
export async function appendSegmentLine(paths, writer, line) {
|
|
109
|
+
assertWriterId(writer);
|
|
110
|
+
const file = segmentFile(paths, writer);
|
|
111
|
+
const created = !(await exists(file));
|
|
112
|
+
const handle = await open(file, 'a', 0o600);
|
|
113
|
+
try {
|
|
114
|
+
await handle.appendFile(`${JSON.stringify(line)}\n`, 'utf8');
|
|
115
|
+
await handle.sync();
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
await handle.close();
|
|
119
|
+
}
|
|
120
|
+
if (created)
|
|
121
|
+
await syncDirectory(paths.segments);
|
|
122
|
+
}
|
|
123
|
+
/** Atomically replace a whole segment (purge only). The caller holds the writer lock. */
|
|
124
|
+
export async function replaceSegment(paths, writer, lines) {
|
|
125
|
+
await replaceFile(paths.segments, segmentFile(paths, writer), lines.map(line => `${line}\n`).join(''));
|
|
126
|
+
}
|
|
127
|
+
/** Write a 0600 file through a temporary name, fsync and rename. */
|
|
128
|
+
export async function replaceFile(directory, file, contents) {
|
|
129
|
+
const temporary = `${file}.${process.pid}.${randomBytes(6).toString('hex')}.tmp`;
|
|
130
|
+
const handle = await open(temporary, 'wx', 0o600);
|
|
131
|
+
try {
|
|
132
|
+
await handle.writeFile(contents, 'utf8');
|
|
133
|
+
await handle.sync();
|
|
134
|
+
}
|
|
135
|
+
finally {
|
|
136
|
+
await handle.close();
|
|
137
|
+
}
|
|
138
|
+
try {
|
|
139
|
+
await rename(temporary, file);
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
await rm(temporary, { force: true });
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
await syncDirectory(directory);
|
|
146
|
+
}
|
|
147
|
+
/** The per-ledger index-naming key, created once with an exclusive publish. */
|
|
148
|
+
export async function loadIndexKey(paths) {
|
|
149
|
+
if (!(await exists(paths.indexKey))) {
|
|
150
|
+
await publishExclusive(paths.root, paths.indexKey, `${randomBytes(32).toString('hex')}\n`, 'index-key');
|
|
151
|
+
}
|
|
152
|
+
const stat = await lstat(paths.indexKey);
|
|
153
|
+
if (!stat.isFile() || (stat.mode & 0o077) !== 0)
|
|
154
|
+
throw integrityError('index-key-permissions');
|
|
155
|
+
const value = (await readFile(paths.indexKey, 'utf8')).trim();
|
|
156
|
+
if (!/^[a-f0-9]{64}$/.test(value))
|
|
157
|
+
throw integrityError('index-key-malformed');
|
|
158
|
+
return Buffer.from(value, 'hex');
|
|
159
|
+
}
|
|
160
|
+
/** Keyed file name, so plaintext effect IDs never appear in directory listings. */
|
|
161
|
+
export function indexName(key, scope, effectId, role) {
|
|
162
|
+
return `${keyedName(key, `aiwg.effect.index/v1/${role}`, [canonicalJson(scope), effectId])}.json`;
|
|
163
|
+
}
|
|
164
|
+
function isIndexEntry(value) {
|
|
165
|
+
const entry = value;
|
|
166
|
+
return !!entry && entry.schemaVersion === INDEX_SCHEMA_VERSION && (entry.role === 'intent' || entry.role === 'outcome')
|
|
167
|
+
&& typeof entry.effectId === 'string' && SHA256_DIGEST_PATTERN.test(entry.payloadDigest)
|
|
168
|
+
&& ['intent', 'completed', 'failed', 'reconciled'].includes(entry.phase)
|
|
169
|
+
&& WRITER_ID_PATTERN.test(entry.writer) && Number.isSafeInteger(entry.seq) && entry.seq >= 0
|
|
170
|
+
&& SHA256_DIGEST_PATTERN.test(entry.recordHash);
|
|
171
|
+
}
|
|
172
|
+
export async function readIndexEntry(paths, name) {
|
|
173
|
+
let raw;
|
|
174
|
+
try {
|
|
175
|
+
raw = await readFile(join(paths.index, name), 'utf8');
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
if (error.code === 'ENOENT')
|
|
179
|
+
return null;
|
|
180
|
+
throw error;
|
|
181
|
+
}
|
|
182
|
+
let value;
|
|
183
|
+
try {
|
|
184
|
+
value = JSON.parse(raw);
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
throw integrityError('index-entry-malformed');
|
|
188
|
+
}
|
|
189
|
+
if (!isIndexEntry(value) || `${canonicalJson(value)}\n` !== raw)
|
|
190
|
+
throw integrityError('index-entry-malformed');
|
|
191
|
+
return value;
|
|
192
|
+
}
|
|
193
|
+
export async function listIndexEntries(paths) {
|
|
194
|
+
let names;
|
|
195
|
+
try {
|
|
196
|
+
names = await readdir(paths.index);
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
if (error.code === 'ENOENT')
|
|
200
|
+
return [];
|
|
201
|
+
throw error;
|
|
202
|
+
}
|
|
203
|
+
const out = [];
|
|
204
|
+
for (const name of names.filter(file => /^[a-f0-9]{64}\.json$/.test(file)).sort()) {
|
|
205
|
+
const entry = await readIndexEntry(paths, name);
|
|
206
|
+
if (entry)
|
|
207
|
+
out.push({ name, entry });
|
|
208
|
+
}
|
|
209
|
+
return out;
|
|
210
|
+
}
|
|
211
|
+
/** Exclusive create. Returns false when another writer already claimed the name. */
|
|
212
|
+
export async function claimIndex(paths, name, entry) {
|
|
213
|
+
return publishExclusive(paths.index, join(paths.index, name), `${canonicalJson(entry)}\n`, 'index');
|
|
214
|
+
}
|
|
215
|
+
export async function writePending(paths, writer, pending) {
|
|
216
|
+
await replaceFile(paths.segments, pendingFile(paths, writer), `${canonicalJson(pending)}\n`);
|
|
217
|
+
}
|
|
218
|
+
export async function readPending(paths, writer) {
|
|
219
|
+
let raw;
|
|
220
|
+
try {
|
|
221
|
+
raw = await readFile(pendingFile(paths, writer), 'utf8');
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
if (error.code === 'ENOENT')
|
|
225
|
+
return null;
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
const value = JSON.parse(raw);
|
|
230
|
+
return typeof value?.indexName === 'string' && value.line && typeof value.line === 'object' ? value : null;
|
|
231
|
+
}
|
|
232
|
+
catch {
|
|
233
|
+
return null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
export async function clearPending(paths, writer) {
|
|
237
|
+
await rm(pendingFile(paths, writer), { force: true });
|
|
238
|
+
}
|
|
239
|
+
//# sourceMappingURL=store.js.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Effect ledger v1 vocabulary: the identifiers, phases, kinds and reason codes
|
|
3
|
+
* pinned by `docs/contracts/effect-ledger.v1.md` and `schemas/effects/*`.
|
|
4
|
+
*/
|
|
5
|
+
export const EFFECT_PREDICATE_TYPE = 'https://aiwg.io/attestations/effect/v1';
|
|
6
|
+
export const IN_TOTO_STATEMENT_TYPE = 'https://in-toto.io/Statement/v1';
|
|
7
|
+
export const STATEMENT_PAYLOAD_TYPE = 'application/vnd.in-toto+json';
|
|
8
|
+
export const CHECKPOINT_PAYLOAD_TYPE = 'application/vnd.aiwg.effect-checkpoint.v1+json';
|
|
9
|
+
export const ROTATION_PAYLOAD_TYPE = 'application/vnd.aiwg.effect-key-rotation.v1+json';
|
|
10
|
+
export const RECORD_SCHEMA_VERSION = 'aiwg.effect.record.v1';
|
|
11
|
+
export const SEGMENT_LINE_SCHEMA_VERSION = 'aiwg.effect.segment-line.v1';
|
|
12
|
+
export const CHECKPOINT_SCHEMA_VERSION = 'aiwg.effect.checkpoint.v1';
|
|
13
|
+
export const KEYRING_SCHEMA_VERSION = 'aiwg.effect.keyring.v1';
|
|
14
|
+
export const ROTATION_SCHEMA_VERSION = 'aiwg.effect.key-rotation.v1';
|
|
15
|
+
export const VERIFIER_RESULT_SCHEMA_VERSION = 'aiwg.effect.verifier-result.v1';
|
|
16
|
+
export const INDEX_SCHEMA_VERSION = 'aiwg.effect.index.v1';
|
|
17
|
+
export const EFFECT_SUBSYSTEMS = ['review', 'job', 'delivery', 'custom'];
|
|
18
|
+
export const EFFECT_PHASES = ['intent', 'completed', 'failed', 'reconciled', 'tombstone'];
|
|
19
|
+
export const CORE_EFFECT_KINDS = [
|
|
20
|
+
'git.commit',
|
|
21
|
+
'git.tag',
|
|
22
|
+
'file.digest',
|
|
23
|
+
'decision.receipt',
|
|
24
|
+
'decision.review.continuation',
|
|
25
|
+
'tracker.comment',
|
|
26
|
+
'tracker.issue.closed',
|
|
27
|
+
'tracker.pr.merged',
|
|
28
|
+
];
|
|
29
|
+
export const EFFECT_ID_DERIVATION_NAMES = ['aiwg.effect/v1', 'd13.review/v1'];
|
|
30
|
+
export const PRESENT_REASONS = ['marker-match', 'state-match', 'digest-match', 'heuristic-match'];
|
|
31
|
+
export const ABSENT_REASONS = ['complete-query-no-match'];
|
|
32
|
+
export const UNKNOWN_REASONS = [
|
|
33
|
+
'verifier-missing',
|
|
34
|
+
'verifier-version-mismatch',
|
|
35
|
+
'verifier-cannot-report-absent',
|
|
36
|
+
'network-error',
|
|
37
|
+
'timeout',
|
|
38
|
+
'auth-denied',
|
|
39
|
+
'rate-limited',
|
|
40
|
+
'server-error',
|
|
41
|
+
'container-unreadable',
|
|
42
|
+
'paging-incomplete',
|
|
43
|
+
'consistency-lag',
|
|
44
|
+
'tracker-blocked',
|
|
45
|
+
'malformed-response',
|
|
46
|
+
'evidence-conflict',
|
|
47
|
+
];
|
|
48
|
+
export const FAILURE_REASONS = ['target-rejected', 'precondition-failed', 'cancelled-before-dispatch'];
|
|
49
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in `decision.receipt` verifier over the D03 receipt stores and D16 job
|
|
3
|
+
* items. The `decision.review.continuation` verifier lives in `./review.ts`.
|
|
4
|
+
*
|
|
5
|
+
* Target forms (`decision:<receipt-id>`):
|
|
6
|
+
*
|
|
7
|
+
* | Target | Source | Store scope from |
|
|
8
|
+
* |---|---|---|
|
|
9
|
+
* | `decision:invocation/<invocationId>` | D03 `DecisionReceiptStore.read` | `projectId` |
|
|
10
|
+
* | `decision:batch/<batchId>` | D03 `BatchReceiptStore.read` | `tenantId`, `projectId` |
|
|
11
|
+
* | `decision:job/<jobId>/<itemId>` | D16 `JobStore.read`, then the D03 receipt of the item's latest attempt | `tenantId`, `projectId`, `workspaceId`, `principalId` |
|
|
12
|
+
*
|
|
13
|
+
* Scope members come from the effect context, falling back to the verifier
|
|
14
|
+
* options. The expected receipt digest (`artifactDigest` of the receipt) comes
|
|
15
|
+
* from `expected.digest`, then `context.receiptDigest`; for a job item the
|
|
16
|
+
* attempt's recorded `receiptDigest` wins.
|
|
17
|
+
*
|
|
18
|
+
* @see docs/contracts/effect-ledger.v1.md "Built-in verifiers"
|
|
19
|
+
*/
|
|
20
|
+
import { DecisionReceiptAccessError } from '../../decision/receipts.js';
|
|
21
|
+
import { artifactDigest } from '../../decision/validate.js';
|
|
22
|
+
export const DECISION_RECEIPT_VERIFIER_VERSION = '1.0.0';
|
|
23
|
+
export const REVIEW_CONTINUATION_PLACEHOLDER_VERSION = '0.1.0';
|
|
24
|
+
const DIGEST = /^sha256:[a-f0-9]{64}$/;
|
|
25
|
+
const SEGMENT = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,255}$/;
|
|
26
|
+
const unknown = (reason, evidence) => ({ result: 'unknown', reason, complete: false, ...(evidence ? { evidence } : {}) });
|
|
27
|
+
const absent = (evidence) => ({ result: 'absent', reason: 'complete-query-no-match', complete: true, evidence });
|
|
28
|
+
function member(request, options, name) {
|
|
29
|
+
const value = request.context[name] ?? options[name];
|
|
30
|
+
return typeof value === 'string' && value ? value : undefined;
|
|
31
|
+
}
|
|
32
|
+
function expectedDigest(request) {
|
|
33
|
+
const value = request.expected.digest ?? request.context.receiptDigest;
|
|
34
|
+
if (value === undefined)
|
|
35
|
+
return undefined;
|
|
36
|
+
return typeof value === 'string' && DIGEST.test(value) ? value : null;
|
|
37
|
+
}
|
|
38
|
+
/** Store failures are never `absent`: denial is `auth-denied`, anything else (MAC, corruption, I/O) `container-unreadable`. */
|
|
39
|
+
function storeFailure(error) {
|
|
40
|
+
if (error instanceof DecisionReceiptAccessError)
|
|
41
|
+
return unknown('auth-denied');
|
|
42
|
+
return unknown('container-unreadable');
|
|
43
|
+
}
|
|
44
|
+
async function read(load) {
|
|
45
|
+
try {
|
|
46
|
+
return { value: await load() };
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
return { failure: storeFailure(error) };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
function digestOf(value) {
|
|
53
|
+
try {
|
|
54
|
+
return artifactDigest(value);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** Settle a D03 state machine: completed is present, failed is absent, anything else is unsettled. */
|
|
61
|
+
function settle(state, receipt, expected, base) {
|
|
62
|
+
if (state === 'completed') {
|
|
63
|
+
const actual = digestOf(receipt);
|
|
64
|
+
if (!actual)
|
|
65
|
+
return unknown('malformed-response');
|
|
66
|
+
const evidence = { ...base, state, receiptDigest: actual, expectedDigest: expected ?? null };
|
|
67
|
+
if (expected === undefined)
|
|
68
|
+
return { result: 'present', reason: 'state-match', complete: true, evidence };
|
|
69
|
+
return actual === expected
|
|
70
|
+
? { result: 'present', reason: 'digest-match', complete: true, evidence }
|
|
71
|
+
: unknown('evidence-conflict', evidence);
|
|
72
|
+
}
|
|
73
|
+
if (state === 'failed')
|
|
74
|
+
return absent({ ...base, state });
|
|
75
|
+
return unknown('consistency-lag', { ...base, state });
|
|
76
|
+
}
|
|
77
|
+
/** `decision.receipt`: D03 invocation and batch receipts, and D16 job items. */
|
|
78
|
+
export function decisionReceiptVerifier(options = {}) {
|
|
79
|
+
return {
|
|
80
|
+
kind: 'decision.receipt',
|
|
81
|
+
version: DECISION_RECEIPT_VERIFIER_VERSION,
|
|
82
|
+
canReportAbsent: true,
|
|
83
|
+
async verify(request) {
|
|
84
|
+
if (!request.target.startsWith('decision:'))
|
|
85
|
+
return unknown('malformed-response');
|
|
86
|
+
const parts = request.target.slice('decision:'.length).split('/');
|
|
87
|
+
const expected = expectedDigest(request);
|
|
88
|
+
if (expected === null)
|
|
89
|
+
return unknown('malformed-response');
|
|
90
|
+
if (!parts.slice(1).every(part => SEGMENT.test(part)))
|
|
91
|
+
return unknown('malformed-response');
|
|
92
|
+
if (parts[0] === 'invocation' && parts.length === 2) {
|
|
93
|
+
const projectId = member(request, options, 'projectId');
|
|
94
|
+
if (!options.receipts || !projectId)
|
|
95
|
+
return unknown('container-unreadable');
|
|
96
|
+
const loaded = await read(() => options.receipts.read(parts[1], projectId));
|
|
97
|
+
if ('failure' in loaded)
|
|
98
|
+
return loaded.failure;
|
|
99
|
+
const base = { source: 'd03-invocation', invocationId: parts[1] };
|
|
100
|
+
if (!loaded.value)
|
|
101
|
+
return absent({ ...base, found: false });
|
|
102
|
+
const fingerprint = request.context.fingerprint;
|
|
103
|
+
if (typeof fingerprint === 'string' && fingerprint !== loaded.value.fingerprint)
|
|
104
|
+
return unknown('evidence-conflict', { ...base, found: true });
|
|
105
|
+
return settle(loaded.value.state, loaded.value, expected, base);
|
|
106
|
+
}
|
|
107
|
+
if (parts[0] === 'batch' && parts.length === 2) {
|
|
108
|
+
const tenantId = member(request, options, 'tenantId');
|
|
109
|
+
const projectId = member(request, options, 'projectId');
|
|
110
|
+
if (!options.batches || !tenantId || !projectId)
|
|
111
|
+
return unknown('container-unreadable');
|
|
112
|
+
const loaded = await read(() => options.batches.read(parts[1], tenantId, projectId));
|
|
113
|
+
if ('failure' in loaded)
|
|
114
|
+
return loaded.failure;
|
|
115
|
+
const base = { source: 'd03-batch', batchId: parts[1] };
|
|
116
|
+
if (!loaded.value)
|
|
117
|
+
return absent({ ...base, found: false });
|
|
118
|
+
return settle(loaded.value.status, loaded.value, expected, base);
|
|
119
|
+
}
|
|
120
|
+
if (parts[0] === 'job' && parts.length === 3) {
|
|
121
|
+
const scope = {
|
|
122
|
+
tenantId: member(request, options, 'tenantId'), projectId: member(request, options, 'projectId'),
|
|
123
|
+
workspaceId: member(request, options, 'workspaceId'), principalId: member(request, options, 'principalId'),
|
|
124
|
+
};
|
|
125
|
+
if (!options.jobs || !options.receipts || Object.values(scope).some(value => value === undefined))
|
|
126
|
+
return unknown('container-unreadable');
|
|
127
|
+
const [, jobId, itemId] = parts;
|
|
128
|
+
const base = { source: 'd16-job-item', jobId, itemId };
|
|
129
|
+
const job = await read(() => options.jobs.read(scope, jobId));
|
|
130
|
+
if ('failure' in job)
|
|
131
|
+
return job.failure;
|
|
132
|
+
if (!job.value)
|
|
133
|
+
return absent({ ...base, found: false });
|
|
134
|
+
// A deleted (D10-tombstoned) job cannot speak for its items.
|
|
135
|
+
if (job.value.deleted)
|
|
136
|
+
return unknown('container-unreadable', { ...base, deleted: true });
|
|
137
|
+
const item = job.value.job.items.find(candidate => candidate.id === itemId);
|
|
138
|
+
if (!item)
|
|
139
|
+
return absent({ ...base, found: false });
|
|
140
|
+
const attempt = item.attempts.at(-1);
|
|
141
|
+
if (!attempt)
|
|
142
|
+
return absent({ ...base, itemState: item.state, attempts: 0 });
|
|
143
|
+
const receipt = await read(() => options.receipts.read(attempt.id, scope.projectId));
|
|
144
|
+
if ('failure' in receipt)
|
|
145
|
+
return receipt.failure;
|
|
146
|
+
const attemptBase = { ...base, itemState: item.state, attemptId: attempt.id, attemptOutcome: attempt.outcome };
|
|
147
|
+
if (!receipt.value)
|
|
148
|
+
return absent({ ...attemptBase, found: false });
|
|
149
|
+
return settle(receipt.value.state, receipt.value, attempt.receiptDigest ?? expected, attemptBase);
|
|
150
|
+
}
|
|
151
|
+
return unknown('malformed-response');
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* @deprecated Replaced by `reviewContinuationVerifier` (#2721) and no longer a
|
|
157
|
+
* built-in. It always returns `unknown` / `verifier-missing` and can never
|
|
158
|
+
* report `absent`; kept only so existing imports keep compiling.
|
|
159
|
+
*/
|
|
160
|
+
export function reviewContinuationPlaceholderVerifier() {
|
|
161
|
+
return {
|
|
162
|
+
kind: 'decision.review.continuation',
|
|
163
|
+
version: REVIEW_CONTINUATION_PLACEHOLDER_VERSION,
|
|
164
|
+
canReportAbsent: false,
|
|
165
|
+
async verify() { return unknown('verifier-missing'); },
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=decision.js.map
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in `file.digest` verifier: a realpath-contained regular file exists
|
|
3
|
+
* with the expected SHA-256.
|
|
4
|
+
*
|
|
5
|
+
* Target `file:<path>@sha256:<hex>`; `<path>` is relative to the configured
|
|
6
|
+
* root (an absolute path must lie inside it). A lexical or symlink escape from
|
|
7
|
+
* the root is `unknown` / `container-unreadable`, never `absent`.
|
|
8
|
+
*
|
|
9
|
+
* @see docs/contracts/effect-ledger.v1.md "Built-in verifiers"
|
|
10
|
+
*/
|
|
11
|
+
import { createHash } from 'node:crypto';
|
|
12
|
+
import { createReadStream } from 'node:fs';
|
|
13
|
+
import { lstat, realpath } from 'node:fs/promises';
|
|
14
|
+
import { dirname, isAbsolute, relative, resolve, sep } from 'node:path';
|
|
15
|
+
export const FILE_DIGEST_VERIFIER_VERSION = '1.0.0';
|
|
16
|
+
const TARGET = /^file:(.+)@(sha256:[a-f0-9]{64})$/;
|
|
17
|
+
const unknown = (reason) => ({ result: 'unknown', reason, complete: false });
|
|
18
|
+
function inside(root, candidate) {
|
|
19
|
+
const rel = relative(root, candidate);
|
|
20
|
+
return rel === '' ? false : !rel.startsWith(`..${sep}`) && rel !== '..' && !isAbsolute(rel);
|
|
21
|
+
}
|
|
22
|
+
const errno = (error) => error?.code;
|
|
23
|
+
async function sha256File(path, signal) {
|
|
24
|
+
const hash = createHash('sha256');
|
|
25
|
+
for await (const chunk of createReadStream(path, { signal }))
|
|
26
|
+
hash.update(chunk);
|
|
27
|
+
return `sha256:${hash.digest('hex')}`;
|
|
28
|
+
}
|
|
29
|
+
export function fileDigestVerifier(options) {
|
|
30
|
+
if (!options || typeof options.root !== 'string' || !options.root)
|
|
31
|
+
throw new TypeError('file.digest verifier needs a root');
|
|
32
|
+
const configuredRoot = resolve(options.root);
|
|
33
|
+
return {
|
|
34
|
+
kind: 'file.digest',
|
|
35
|
+
version: FILE_DIGEST_VERIFIER_VERSION,
|
|
36
|
+
canReportAbsent: true,
|
|
37
|
+
async verify(request) {
|
|
38
|
+
const match = TARGET.exec(request.target);
|
|
39
|
+
if (!match)
|
|
40
|
+
return unknown('malformed-response');
|
|
41
|
+
const [, path, digest] = match;
|
|
42
|
+
const expected = request.expected.digest ?? request.context.digest;
|
|
43
|
+
if (expected !== undefined && expected !== digest)
|
|
44
|
+
return unknown('evidence-conflict');
|
|
45
|
+
let root;
|
|
46
|
+
try {
|
|
47
|
+
root = await realpath(configuredRoot);
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return unknown('container-unreadable');
|
|
51
|
+
}
|
|
52
|
+
// An absolute path spelled under the configured (non-canonical) root is rebased onto its realpath.
|
|
53
|
+
const lexical = isAbsolute(path) && inside(configuredRoot, resolve(path))
|
|
54
|
+
? resolve(root, relative(configuredRoot, resolve(path)))
|
|
55
|
+
: resolve(root, path);
|
|
56
|
+
if (!inside(root, lexical))
|
|
57
|
+
return unknown('container-unreadable');
|
|
58
|
+
let real;
|
|
59
|
+
try {
|
|
60
|
+
real = await realpath(lexical);
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
if (errno(error) !== 'ENOENT' && errno(error) !== 'ENOTDIR')
|
|
64
|
+
return unknown('container-unreadable');
|
|
65
|
+
// Missing: `absent` only when the nearest existing ancestor is itself contained.
|
|
66
|
+
let parent = dirname(lexical);
|
|
67
|
+
for (;;) {
|
|
68
|
+
try {
|
|
69
|
+
const realParent = await realpath(parent);
|
|
70
|
+
if (realParent !== root && !inside(root, realParent))
|
|
71
|
+
return unknown('container-unreadable');
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
catch (inner) {
|
|
75
|
+
if (errno(inner) !== 'ENOENT' && errno(inner) !== 'ENOTDIR')
|
|
76
|
+
return unknown('container-unreadable');
|
|
77
|
+
if (parent === dirname(parent))
|
|
78
|
+
return unknown('container-unreadable');
|
|
79
|
+
parent = dirname(parent);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const evidence = { path, expectedDigest: digest, found: false };
|
|
83
|
+
return { result: 'absent', reason: 'complete-query-no-match', complete: true, evidence };
|
|
84
|
+
}
|
|
85
|
+
if (!inside(root, real))
|
|
86
|
+
return unknown('container-unreadable');
|
|
87
|
+
let actual;
|
|
88
|
+
try {
|
|
89
|
+
const stats = await lstat(real);
|
|
90
|
+
if (!stats.isFile())
|
|
91
|
+
return unknown('evidence-conflict');
|
|
92
|
+
actual = await sha256File(real, request.signal);
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (request.signal.aborted)
|
|
96
|
+
throw error;
|
|
97
|
+
return unknown('container-unreadable');
|
|
98
|
+
}
|
|
99
|
+
const evidence = { path, expectedDigest: digest, actualDigest: actual, found: true };
|
|
100
|
+
return actual === digest
|
|
101
|
+
? { result: 'present', reason: 'digest-match', complete: true, evidence }
|
|
102
|
+
: { result: 'absent', reason: 'complete-query-no-match', complete: true, evidence };
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=file.js.map
|