@aiwg/cli 2026.9.20 → 2026.9.24
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/cli.js +23 -18
- package/dist/src/artifacts/enrichment/cli.js +1 -1
- package/dist/src/artifacts/fortemi-core-query-adapter.js +6 -2
- package/dist/src/artifacts/query-engine.js +14 -4
- 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 +282 -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,252 @@
|
|
|
1
|
+
import { createHash, createHmac, randomUUID, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { link, mkdir, open, readdir, readFile, rm } from 'node:fs/promises';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { canonicalJson } from '../../security/artifact-trust.js';
|
|
5
|
+
import { importDiscoveryManifest } from '../../sessions/batch-import.js';
|
|
6
|
+
import { assertReviewProjection, ReviewConflictError, reviewDigest } from './validate.js';
|
|
7
|
+
/** Caller supplies an authorized, exact-workspace refresh; never discovers provider roots implicitly. */
|
|
8
|
+
export class WorkspaceReviewSessionAudit {
|
|
9
|
+
repository;
|
|
10
|
+
refresh;
|
|
11
|
+
locate;
|
|
12
|
+
covered = new Set();
|
|
13
|
+
constructor(repository, refresh, locate) {
|
|
14
|
+
this.repository = repository;
|
|
15
|
+
this.refresh = refresh;
|
|
16
|
+
this.locate = locate;
|
|
17
|
+
}
|
|
18
|
+
exactCoverage(workspaceId, previousSessionId) {
|
|
19
|
+
const coverage = this.repository.getCoverage(workspaceId);
|
|
20
|
+
if (coverage.status === 'stale')
|
|
21
|
+
return 'stale';
|
|
22
|
+
const session = this.repository.getSession(previousSessionId, workspaceId);
|
|
23
|
+
if (!session)
|
|
24
|
+
return 'unavailable';
|
|
25
|
+
if (!coverage.manifestId || session.consistency === 'provisional')
|
|
26
|
+
return 'partial';
|
|
27
|
+
const run = this.repository.getBatchImportRunForManifest(coverage.manifestId, workspaceId);
|
|
28
|
+
const source = run?.sources.find(item => item.sourceId === session.sourceId && item.provider === session.provider);
|
|
29
|
+
// Other providers may legitimately require a separate export. Global
|
|
30
|
+
// workspace coverage can be partial while this exact committed source is
|
|
31
|
+
// covered. No session from an uncommitted/rejected source is accepted.
|
|
32
|
+
return source && ['committed', 'previously-committed', 'duplicate'].includes(source.status) &&
|
|
33
|
+
['complete', 'partial'].includes(run.status) ? 'covered' : 'partial';
|
|
34
|
+
}
|
|
35
|
+
async hydrate(workspaceId, previousSessionId) {
|
|
36
|
+
this.covered.delete(`${workspaceId}\0${previousSessionId}`);
|
|
37
|
+
await this.refresh(workspaceId, previousSessionId);
|
|
38
|
+
const status = this.exactCoverage(workspaceId, previousSessionId);
|
|
39
|
+
if (status === 'covered')
|
|
40
|
+
this.covered.add(`${workspaceId}\0${previousSessionId}`);
|
|
41
|
+
return { workspaceId, previousSessionId, coverage: status };
|
|
42
|
+
}
|
|
43
|
+
async findAttempt(query) {
|
|
44
|
+
if (!this.covered.has(`${query.workspaceId}\0${query.previousSessionId}`) ||
|
|
45
|
+
this.exactCoverage(query.workspaceId, query.previousSessionId) !== 'covered')
|
|
46
|
+
return null;
|
|
47
|
+
const matches = this.repository.listEvents(query.previousSessionId, query.workspaceId)
|
|
48
|
+
.filter(event => {
|
|
49
|
+
if (event.origin !== 'tool-control' || event.consistency !== 'complete')
|
|
50
|
+
return false;
|
|
51
|
+
const marker = this.locate(event);
|
|
52
|
+
return marker?.reviewId === query.reviewId && marker.effectId === query.effectId;
|
|
53
|
+
});
|
|
54
|
+
// An ambiguous marker never attests a specific attempt. Transcript text is
|
|
55
|
+
// not a completion receipt; the independent executor ledger is mandatory.
|
|
56
|
+
return matches.length === 1 ? { workspaceId: query.workspaceId, sessionId: query.previousSessionId,
|
|
57
|
+
reviewId: query.reviewId, effectId: query.effectId } : null;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** Import only a previously reviewed manifest after fresh workspace/session authorization. */
|
|
61
|
+
export function authorizedReviewCatalogRefresh(input) {
|
|
62
|
+
return async (workspaceId, previousSessionId) => {
|
|
63
|
+
if (workspaceId !== input.manifest.workspaceId || !previousSessionId ||
|
|
64
|
+
!await input.authorize(workspaceId, previousSessionId, input.manifest)) {
|
|
65
|
+
throw new Error('Review catalog refresh denied');
|
|
66
|
+
}
|
|
67
|
+
await importDiscoveryManifest({ manifest: input.manifest, repository: input.repository });
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Wrap the effectful executor so the journal records an `intent` BEFORE the
|
|
72
|
+
* effect and a verified `completed` after it.
|
|
73
|
+
*
|
|
74
|
+
* - A known completed receipt is returned without re-executing.
|
|
75
|
+
* - An earlier intent without an outcome (a stale lease) is reconciled through
|
|
76
|
+
* the kind verifier: `present` returns the recovered result; `absent` stays
|
|
77
|
+
* uncertain unless the target is declared idempotent on the effect ID;
|
|
78
|
+
* `unknown` never replays. Both refusals leave the review in `resuming`.
|
|
79
|
+
* - A journal without `recordIntent` (the deprecated HMAC journal) keeps its
|
|
80
|
+
* post-execution journaling only.
|
|
81
|
+
*/
|
|
82
|
+
export function journaledReviewExecutor(input) {
|
|
83
|
+
return async (effectId, action) => {
|
|
84
|
+
if (effectId !== reviewDigest({ reviewId: input.reviewId, continuationId: input.continuationId,
|
|
85
|
+
proposalVersion: input.proposalVersion }))
|
|
86
|
+
throw new Error('Executor effect identity mismatch');
|
|
87
|
+
const query = { tenantId: input.scope.tenantId, projectId: input.scope.projectId, reviewId: input.reviewId, effectId };
|
|
88
|
+
const known = (receipt) => {
|
|
89
|
+
if (receipt.continuationId !== input.continuationId || receipt.proposalVersion !== input.proposalVersion) {
|
|
90
|
+
throw new Error('Executor receipt identity mismatch');
|
|
91
|
+
}
|
|
92
|
+
return receipt.result;
|
|
93
|
+
};
|
|
94
|
+
const existing = await input.ledger.completedReceipt(query);
|
|
95
|
+
if (existing)
|
|
96
|
+
return known(existing);
|
|
97
|
+
if (input.ledger.recordIntent) {
|
|
98
|
+
const identity = { tenantId: query.tenantId, projectId: query.projectId, reviewId: input.reviewId,
|
|
99
|
+
continuationId: input.continuationId, proposalVersion: input.proposalVersion };
|
|
100
|
+
const state = await input.ledger.recordIntent(identity, reviewDigest(action), input.links);
|
|
101
|
+
if (state === 'pending') {
|
|
102
|
+
const settled = input.ledger.reconcileReceipt
|
|
103
|
+
? await input.ledger.reconcileReceipt(query, { identity: { continuationId: input.continuationId, proposalVersion: input.proposalVersion } })
|
|
104
|
+
: { result: 'unknown', receipt: null };
|
|
105
|
+
if (settled.result === 'present' && settled.receipt)
|
|
106
|
+
return known(settled.receipt);
|
|
107
|
+
if (settled.result !== 'absent' || !input.idempotentTarget) {
|
|
108
|
+
throw new ReviewConflictError(settled.result === 'absent' ? 'Effect outcome remains uncertain' : 'Effect outcome remains unknown');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const result = await input.executeEffect(effectId, structuredClone(action));
|
|
113
|
+
await input.ledger.recordCompleted(query, { effectId, continuationId: input.continuationId,
|
|
114
|
+
proposalVersion: input.proposalVersion, completedAtEpochMs: input.now(), result });
|
|
115
|
+
return result;
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* HMAC-only executor receipt journal.
|
|
120
|
+
*
|
|
121
|
+
* @deprecated Compatibility shim. Use the effect-ledger adapter
|
|
122
|
+
* (`LedgerReviewEffectJournal` / `openReviewEffectLedger`), which records a
|
|
123
|
+
* signed intent before the effect. Migrate existing receipts with
|
|
124
|
+
* `LedgerReviewEffectJournal.importLegacyReceipts`; see docs/decision/review.md.
|
|
125
|
+
* Keep its key separate from review-store and session-index keys.
|
|
126
|
+
*/
|
|
127
|
+
export class FileVerifiedReviewEffectLedger {
|
|
128
|
+
directory;
|
|
129
|
+
integrityKey;
|
|
130
|
+
constructor(directory, integrityKey, options = {}) {
|
|
131
|
+
this.directory = directory;
|
|
132
|
+
this.integrityKey = integrityKey;
|
|
133
|
+
if (integrityKey.length < 32)
|
|
134
|
+
throw new Error('Executor ledger integrity key must be at least 32 bytes');
|
|
135
|
+
for (const other of options.independentOf ?? []) {
|
|
136
|
+
if (other.length === integrityKey.length && timingSafeEqual(Buffer.from(other), Buffer.from(integrityKey))) {
|
|
137
|
+
throw new Error('Executor ledger key must be independent of review-store and session-index keys');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
scope(query) {
|
|
142
|
+
const { tenantId, projectId, reviewId, effectId } = query;
|
|
143
|
+
if (![tenantId, projectId, reviewId, effectId].every(value => typeof value === 'string' && value.length > 0))
|
|
144
|
+
throw new Error('Invalid executor ledger scope');
|
|
145
|
+
return { tenantId, projectId, reviewId, effectId };
|
|
146
|
+
}
|
|
147
|
+
path(query) {
|
|
148
|
+
const digest = createHash('sha256').update(canonicalJson(this.scope(query))).digest('hex');
|
|
149
|
+
return join(this.directory, `${digest}.json`);
|
|
150
|
+
}
|
|
151
|
+
mac(record) { return createHmac('sha256', this.integrityKey).update(canonicalJson(record)).digest('hex'); }
|
|
152
|
+
/** Only the effectful executor calls this after independently verifying completion. */
|
|
153
|
+
async recordCompleted(query, receipt) {
|
|
154
|
+
if (receipt.effectId !== query.effectId || !receipt.continuationId || !Number.isSafeInteger(receipt.proposalVersion) || receipt.proposalVersion < 1 ||
|
|
155
|
+
!Number.isSafeInteger(receipt.completedAtEpochMs) || receipt.completedAtEpochMs < 0)
|
|
156
|
+
throw new Error('Invalid executor completion receipt');
|
|
157
|
+
assertReviewProjection(receipt.result);
|
|
158
|
+
const record = { query: this.scope(query), receipt };
|
|
159
|
+
await mkdir(this.directory, { recursive: true, mode: 0o700 });
|
|
160
|
+
const destination = this.path(query);
|
|
161
|
+
const temporary = `${destination}.${randomUUID()}.tmp`;
|
|
162
|
+
const file = await open(temporary, 'wx', 0o600);
|
|
163
|
+
try {
|
|
164
|
+
await file.writeFile(`${JSON.stringify({ record, mac: this.mac(record) })}\n`);
|
|
165
|
+
await file.sync();
|
|
166
|
+
}
|
|
167
|
+
finally {
|
|
168
|
+
await file.close();
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
await link(temporary, destination);
|
|
172
|
+
const dir = await open(this.directory, 'r');
|
|
173
|
+
try {
|
|
174
|
+
await dir.sync();
|
|
175
|
+
}
|
|
176
|
+
finally {
|
|
177
|
+
await dir.close();
|
|
178
|
+
}
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
if (error.code === 'EEXIST') {
|
|
183
|
+
const existing = await this.completedReceipt(query);
|
|
184
|
+
if (existing && canonicalJson(existing) === canonicalJson(receipt))
|
|
185
|
+
return false;
|
|
186
|
+
throw new Error('Conflicting executor completion receipt');
|
|
187
|
+
}
|
|
188
|
+
throw error;
|
|
189
|
+
}
|
|
190
|
+
finally {
|
|
191
|
+
await rm(temporary, { force: true });
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
async completedReceipt(query) {
|
|
195
|
+
let raw;
|
|
196
|
+
try {
|
|
197
|
+
raw = await readFile(this.path(query), 'utf8');
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
if (error.code === 'ENOENT')
|
|
201
|
+
return null;
|
|
202
|
+
throw error;
|
|
203
|
+
}
|
|
204
|
+
return this.verifyEntry(raw, query);
|
|
205
|
+
}
|
|
206
|
+
/** Every verified receipt in the journal, for migration to the effect ledger. Any invalid entry fails the listing. */
|
|
207
|
+
async listReceipts() {
|
|
208
|
+
let names;
|
|
209
|
+
try {
|
|
210
|
+
names = (await readdir(this.directory)).filter(name => /^[a-f0-9]{64}\.json$/.test(name)).sort();
|
|
211
|
+
}
|
|
212
|
+
catch (error) {
|
|
213
|
+
if (error.code === 'ENOENT')
|
|
214
|
+
return [];
|
|
215
|
+
throw error;
|
|
216
|
+
}
|
|
217
|
+
const out = [];
|
|
218
|
+
for (const name of names) {
|
|
219
|
+
const raw = await readFile(join(this.directory, name), 'utf8');
|
|
220
|
+
let query;
|
|
221
|
+
try {
|
|
222
|
+
query = this.scope(JSON.parse(raw).record.query);
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
throw new Error('Invalid executor ledger entry');
|
|
226
|
+
}
|
|
227
|
+
if (this.path(query) !== join(this.directory, name))
|
|
228
|
+
throw new Error('Invalid executor ledger entry');
|
|
229
|
+
out.push({ query, receipt: this.verifyEntry(raw, query) });
|
|
230
|
+
}
|
|
231
|
+
return out;
|
|
232
|
+
}
|
|
233
|
+
verifyEntry(raw, query) {
|
|
234
|
+
try {
|
|
235
|
+
const envelope = JSON.parse(raw);
|
|
236
|
+
const expected = Buffer.from(this.mac(envelope.record), 'hex');
|
|
237
|
+
const actual = Buffer.from(envelope.mac ?? '', 'hex');
|
|
238
|
+
if (actual.length !== expected.length || !timingSafeEqual(actual, expected) ||
|
|
239
|
+
canonicalJson(envelope.record.query) !== canonicalJson(this.scope(query)) || envelope.record.receipt.effectId !== query.effectId ||
|
|
240
|
+
!envelope.record.receipt.continuationId || !Number.isSafeInteger(envelope.record.receipt.proposalVersion) ||
|
|
241
|
+
envelope.record.receipt.proposalVersion < 1 || !Number.isSafeInteger(envelope.record.receipt.completedAtEpochMs) ||
|
|
242
|
+
envelope.record.receipt.completedAtEpochMs < 0)
|
|
243
|
+
throw new Error('Invalid executor ledger entry');
|
|
244
|
+
assertReviewProjection(envelope.record.receipt.result);
|
|
245
|
+
return structuredClone(envelope.record.receipt);
|
|
246
|
+
}
|
|
247
|
+
catch {
|
|
248
|
+
throw new Error('Invalid executor ledger entry');
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
//# sourceMappingURL=recovery-adapters.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { ReviewConflictError } from './validate.js';
|
|
2
|
+
/**
|
|
3
|
+
* Opt-in recovery adapter for DecisionReviewService.resume's reconcile callback.
|
|
4
|
+
* Missing, stale, conflicting, or unauthenticated evidence returns null; the
|
|
5
|
+
* service will keep the old effect uncertain and will not dispatch it again.
|
|
6
|
+
*/
|
|
7
|
+
export function auditedReviewReconciler(input) {
|
|
8
|
+
return async (effectId) => {
|
|
9
|
+
const coverage = await input.catalog.hydrate(input.workspaceId, input.previousSessionId);
|
|
10
|
+
if (coverage.workspaceId !== input.workspaceId || coverage.previousSessionId !== input.previousSessionId || coverage.coverage !== 'covered')
|
|
11
|
+
return null;
|
|
12
|
+
const attempt = await input.catalog.findAttempt({ workspaceId: input.workspaceId, previousSessionId: input.previousSessionId, reviewId: input.reviewId, effectId });
|
|
13
|
+
if (!attempt || attempt.workspaceId !== input.workspaceId || attempt.sessionId !== input.previousSessionId ||
|
|
14
|
+
attempt.reviewId !== input.reviewId || attempt.effectId !== effectId)
|
|
15
|
+
return null;
|
|
16
|
+
const query = { tenantId: input.scope.tenantId, projectId: input.scope.projectId, reviewId: input.reviewId, effectId };
|
|
17
|
+
let receipt = await input.ledger.completedReceipt(query);
|
|
18
|
+
if (!receipt && input.verifier) {
|
|
19
|
+
const settled = await input.verifier.reconcileReceipt(query);
|
|
20
|
+
// `absent` never authorizes a replay and `unknown` settles nothing.
|
|
21
|
+
if (settled.result !== 'present')
|
|
22
|
+
return null;
|
|
23
|
+
receipt = settled.receipt;
|
|
24
|
+
}
|
|
25
|
+
if (!receipt || receipt.effectId !== effectId || !Number.isSafeInteger(receipt.completedAtEpochMs) || receipt.completedAtEpochMs < 0)
|
|
26
|
+
return null;
|
|
27
|
+
if (input.expected && (receipt.continuationId !== input.expected.continuationId || receipt.proposalVersion !== input.expected.proposalVersion)) {
|
|
28
|
+
throw new ReviewConflictError('Reconciliation receipt mismatch');
|
|
29
|
+
}
|
|
30
|
+
return receipt;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=recovery.js.map
|