@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,321 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* D13 adoption of the AIWG effect ledger (#2721, answers #2677).
|
|
3
|
+
*
|
|
4
|
+
* `LedgerReviewEffectJournal` implements `VerifiedReviewEffectLedger` (and the
|
|
5
|
+
* `ReviewEffectJournal` write port used by `journaledReviewExecutor`) over the
|
|
6
|
+
* signed effect ledger in `src/effects`. It keeps D13's effect identity exactly:
|
|
7
|
+
* the `d13.review/v1` derivation is `reviewDigest({reviewId, continuationId,
|
|
8
|
+
* proposalVersion})`, so reviews persisted before the ledger stay valid.
|
|
9
|
+
*
|
|
10
|
+
* Target systems remain the authorities. The ledger records a signed `intent`
|
|
11
|
+
* before the effect and `completed` only after the `decision.review.continuation`
|
|
12
|
+
* verifier reports `present`; `absent` and `unknown` never authorize a replay.
|
|
13
|
+
*
|
|
14
|
+
* @see docs/decision/review.md "Effect ledger"
|
|
15
|
+
* @see docs/contracts/effect-ledger.v1.md
|
|
16
|
+
*/
|
|
17
|
+
import { createHash, createPrivateKey, createPublicKey } from 'node:crypto';
|
|
18
|
+
import { mkdir, readFile } from 'node:fs/promises';
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
import { canonicalJson } from '../../security/artifact-trust.js';
|
|
21
|
+
import { publishExclusive } from '../../storage/protected-files.js';
|
|
22
|
+
import { EffectLedger, createBuiltinVerifierRegistry, createVerifierRegistry, effectId as deriveEffectId, effectSha256Digest, evidenceDigest, isValidEffectId, lookupEffect, reconcileEffect, recordIntent, reviewContinuationTarget, } from '../../effects/index.js';
|
|
23
|
+
import { readWriterSegment } from '../../effects/reader.js';
|
|
24
|
+
import { assertReviewProjection, reviewDigest, reviewOperatorEventId } from './validate.js';
|
|
25
|
+
export const REVIEW_EFFECT_KIND = 'decision.review.continuation';
|
|
26
|
+
export const REVIEW_EFFECT_DERIVATION = 'd13.review/v1';
|
|
27
|
+
/** `result` of a receipt rebuilt from ledger records when the receipt body was never archived. */
|
|
28
|
+
export const REVIEW_EFFECT_REFERENCE_SCHEMA = 'aiwg.review-effect-reference/v1';
|
|
29
|
+
const ARCHIVE_SCHEMA = 'aiwg.review-effect-receipt/v1';
|
|
30
|
+
/** Version of the one-shot verifier that imports legacy HMAC receipts (`method: legacy-hmac`). */
|
|
31
|
+
export const LEGACY_HMAC_IMPORT_VERIFIER_VERSION = '0.2.0';
|
|
32
|
+
const PKCS8_ED25519_PREFIX = Buffer.from('302e020100300506032b657004220420', 'hex');
|
|
33
|
+
const sha256Hex = (value) => createHash('sha256').update(value).digest('hex');
|
|
34
|
+
const invalidEntry = () => new Error('Invalid executor ledger entry');
|
|
35
|
+
/** The ledger intent (kind, target, context) for one review continuation. */
|
|
36
|
+
export function reviewEffectIntent(identity) {
|
|
37
|
+
return {
|
|
38
|
+
kind: REVIEW_EFFECT_KIND,
|
|
39
|
+
target: reviewContinuationTarget(identity.tenantId, identity.projectId, identity.reviewId),
|
|
40
|
+
context: { reviewId: identity.reviewId, continuationId: identity.continuationId, proposalVersion: identity.proposalVersion },
|
|
41
|
+
derivation: REVIEW_EFFECT_DERIVATION,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** The ledger effect ID for a review continuation; equal to D13's `reviewDigest` identity. */
|
|
45
|
+
export function reviewEffectId(identity) {
|
|
46
|
+
const intent = reviewEffectIntent(identity);
|
|
47
|
+
return deriveEffectId({ scope: { tenant: identity.tenantId, project: identity.projectId, subsystem: 'review' },
|
|
48
|
+
kind: intent.kind, target: intent.target, context: intent.context }, REVIEW_EFFECT_DERIVATION);
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* #1567 links for a review continuation: the operator decision event of the
|
|
52
|
+
* approval of `proposalVersion` (its ID is `reviewOperatorEventId`), plus its
|
|
53
|
+
* audit `record_hash` when the caller has it. Empty when no approval exists.
|
|
54
|
+
*/
|
|
55
|
+
export function reviewApprovalLinks(review, proposalVersion, operatorDecisionRecordHash) {
|
|
56
|
+
const approval = [...review.events].reverse().find(event => event.type === 'approved' && event.proposalVersion === proposalVersion);
|
|
57
|
+
if (!approval)
|
|
58
|
+
return {};
|
|
59
|
+
return {
|
|
60
|
+
operatorDecisionEventId: approval.operatorDecisionEventId ?? reviewOperatorEventId(review.reviewId, approval.sequence),
|
|
61
|
+
...(operatorDecisionRecordHash ? { operatorDecisionRecordHash } : {}),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** Ed25519 key ID the first 32 bytes of `material` would produce as a ledger seed. */
|
|
65
|
+
function seedKeyid(material) {
|
|
66
|
+
if (material.length < 32)
|
|
67
|
+
return null;
|
|
68
|
+
try {
|
|
69
|
+
const key = createPrivateKey({ key: Buffer.concat([PKCS8_ED25519_PREFIX, Buffer.from(material.subarray(0, 32))]), format: 'der', type: 'pkcs8' });
|
|
70
|
+
return effectSha256Digest(createPublicKey(key).export({ format: 'der', type: 'spki' }));
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* `VerifiedReviewEffectLedger` backed by the effect ledger. Scope: the ledger's
|
|
78
|
+
* `{tenant, project}` are the review's `{tenantId, projectId}`, subsystem `review`.
|
|
79
|
+
*/
|
|
80
|
+
export class LedgerReviewEffectJournal {
|
|
81
|
+
ledger;
|
|
82
|
+
options;
|
|
83
|
+
keysChecked = false;
|
|
84
|
+
constructor(ledger, options = {}) {
|
|
85
|
+
this.ledger = ledger;
|
|
86
|
+
this.options = options;
|
|
87
|
+
if (ledger.scope.subsystem !== 'review')
|
|
88
|
+
throw new Error('Review effect ledger must use the review subsystem');
|
|
89
|
+
}
|
|
90
|
+
inScope(query) {
|
|
91
|
+
return query.tenantId === this.ledger.scope.tenant && query.projectId === this.ledger.scope.project;
|
|
92
|
+
}
|
|
93
|
+
async assertIndependentKey() {
|
|
94
|
+
if (this.keysChecked)
|
|
95
|
+
return;
|
|
96
|
+
const { keyid } = await this.ledger.signingKey();
|
|
97
|
+
for (const material of this.options.independentOf ?? []) {
|
|
98
|
+
if (seedKeyid(material) === keyid)
|
|
99
|
+
throw new Error('Effect ledger key must be independent of review-store and session-index keys');
|
|
100
|
+
}
|
|
101
|
+
this.keysChecked = true;
|
|
102
|
+
}
|
|
103
|
+
archivePath(effectId) {
|
|
104
|
+
const directory = join(this.ledger.paths().root, 'receipts');
|
|
105
|
+
return { directory, file: join(directory, `${sha256Hex(effectId)}.json`) };
|
|
106
|
+
}
|
|
107
|
+
/** The verified intent identity behind a lookup; throws on any mismatch. */
|
|
108
|
+
async intentIdentity(lookup) {
|
|
109
|
+
const intent = lookup.records.find(record => record.phase === 'intent');
|
|
110
|
+
if (!intent)
|
|
111
|
+
throw invalidEntry();
|
|
112
|
+
const segment = await readWriterSegment(this.ledger.paths(), intent.writer);
|
|
113
|
+
const decoded = segment?.lines[intent.seq]?.decoded;
|
|
114
|
+
if (!decoded || decoded.line.recordHash !== intent.recordHash)
|
|
115
|
+
throw invalidEntry();
|
|
116
|
+
const predicate = decoded.statement.predicate;
|
|
117
|
+
const context = predicate.context ?? {};
|
|
118
|
+
const identity = {
|
|
119
|
+
tenantId: this.ledger.scope.tenant, projectId: this.ledger.scope.project, reviewId: context.reviewId,
|
|
120
|
+
continuationId: context.continuationId, proposalVersion: context.proposalVersion,
|
|
121
|
+
};
|
|
122
|
+
if (predicate.kind !== REVIEW_EFFECT_KIND || predicate.idDerivation !== REVIEW_EFFECT_DERIVATION
|
|
123
|
+
|| predicate.effectId !== lookup.effectId || reviewEffectId(identity) !== lookup.effectId)
|
|
124
|
+
throw invalidEntry();
|
|
125
|
+
return identity;
|
|
126
|
+
}
|
|
127
|
+
async archived(effectId, completed, identity) {
|
|
128
|
+
let raw;
|
|
129
|
+
try {
|
|
130
|
+
raw = await readFile(this.archivePath(effectId).file, 'utf8');
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
if (error.code === 'ENOENT')
|
|
134
|
+
return null;
|
|
135
|
+
throw error;
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
const value = JSON.parse(raw);
|
|
139
|
+
const receipt = value.receipt;
|
|
140
|
+
if (value.schema !== ARCHIVE_SCHEMA || value.effectId !== effectId || value.recordHash !== completed.recordHash
|
|
141
|
+
|| evidenceDigest(value.evidence) !== completed.verification?.evidenceDigest
|
|
142
|
+
|| value.evidence.expectedDigest !== reviewDigest(receipt)
|
|
143
|
+
|| receipt.effectId !== effectId || receipt.continuationId !== identity.continuationId
|
|
144
|
+
|| receipt.proposalVersion !== identity.proposalVersion
|
|
145
|
+
|| !Number.isSafeInteger(receipt.completedAtEpochMs) || receipt.completedAtEpochMs < 0)
|
|
146
|
+
throw invalidEntry();
|
|
147
|
+
assertReviewProjection(receipt.result);
|
|
148
|
+
return structuredClone(receipt);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
throw invalidEntry();
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
/** Receipt for a signed `completed` record; the archived body when present, else a digest reference. */
|
|
155
|
+
async completedReceipt(query) {
|
|
156
|
+
if (!this.inScope(query) || !isValidEffectId(query.effectId, REVIEW_EFFECT_DERIVATION))
|
|
157
|
+
return null;
|
|
158
|
+
const lookup = await lookupEffect(this.ledger, query.effectId);
|
|
159
|
+
const completed = lookup.records.find(record => record.phase === 'completed');
|
|
160
|
+
if (!completed)
|
|
161
|
+
return null;
|
|
162
|
+
const identity = await this.intentIdentity(lookup);
|
|
163
|
+
if (identity.reviewId !== query.reviewId)
|
|
164
|
+
return null;
|
|
165
|
+
const body = await this.archived(query.effectId, completed, identity);
|
|
166
|
+
if (body)
|
|
167
|
+
return body;
|
|
168
|
+
const completedAtEpochMs = Date.parse(completed.recordedAt);
|
|
169
|
+
if (!Number.isSafeInteger(completedAtEpochMs))
|
|
170
|
+
throw invalidEntry();
|
|
171
|
+
return {
|
|
172
|
+
effectId: query.effectId, continuationId: identity.continuationId, proposalVersion: identity.proposalVersion, completedAtEpochMs,
|
|
173
|
+
result: { effectLedger: { schema: REVIEW_EFFECT_REFERENCE_SCHEMA, recordHash: completed.recordHash, keyid: completed.keyid,
|
|
174
|
+
evidenceDigest: completed.verification?.evidenceDigest ?? null } },
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/** Append the signed intent before the effect. `pending`: an earlier intent has no outcome yet. */
|
|
178
|
+
async recordIntent(identity, actionDigest, links) {
|
|
179
|
+
if (!this.inScope(identity))
|
|
180
|
+
throw new Error('Invalid executor ledger scope');
|
|
181
|
+
await this.assertIndependentKey();
|
|
182
|
+
const intent = reviewEffectIntent(identity);
|
|
183
|
+
const receipt = await recordIntent(this.ledger, { ...intent, payloadDigest: actionDigest, ...(links ? { links } : {}) });
|
|
184
|
+
if (receipt.effectId !== reviewDigest({ reviewId: identity.reviewId, continuationId: identity.continuationId,
|
|
185
|
+
proposalVersion: identity.proposalVersion }))
|
|
186
|
+
throw invalidEntry();
|
|
187
|
+
return receipt.idempotent ? 'pending' : 'recorded';
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Reconcile through the kind verifier; every call appends one signed
|
|
191
|
+
* `reconciled` record and `present` also records `completed`. With no intent
|
|
192
|
+
* the effect is `unknown` unless the caller supplies the identity and action
|
|
193
|
+
* digest, in which case the intent is recorded first (a crash before intent).
|
|
194
|
+
*/
|
|
195
|
+
async reconcileReceipt(query, options = {}) {
|
|
196
|
+
const unknown = { result: 'unknown', receipt: null };
|
|
197
|
+
if (!this.inScope(query) || !isValidEffectId(query.effectId, REVIEW_EFFECT_DERIVATION))
|
|
198
|
+
return unknown;
|
|
199
|
+
const lookup = await lookupEffect(this.ledger, query.effectId);
|
|
200
|
+
if (!lookup.records.some(record => record.phase === 'intent')) {
|
|
201
|
+
if (!options.identity || !options.actionDigest)
|
|
202
|
+
return unknown;
|
|
203
|
+
const identity = { tenantId: query.tenantId, projectId: query.projectId, reviewId: query.reviewId, ...options.identity };
|
|
204
|
+
if (reviewEffectId(identity) !== query.effectId)
|
|
205
|
+
return unknown;
|
|
206
|
+
await this.recordIntent(identity, options.actionDigest);
|
|
207
|
+
}
|
|
208
|
+
else if ((await this.intentIdentity(lookup)).reviewId !== query.reviewId)
|
|
209
|
+
return unknown;
|
|
210
|
+
await this.assertIndependentKey();
|
|
211
|
+
const expectedDigest = options.receipt ? reviewDigest(options.receipt) : undefined;
|
|
212
|
+
const outcome = await reconcileEffect(this.ledger, query.effectId, {
|
|
213
|
+
verifiers: this.options.verifiers, timeoutMs: this.options.timeoutMs, expected: expectedDigest ? { digest: expectedDigest } : {},
|
|
214
|
+
});
|
|
215
|
+
const result = outcome.result.verification.result;
|
|
216
|
+
if (result !== 'present')
|
|
217
|
+
return { result, receipt: null };
|
|
218
|
+
if (options.receipt && outcome.completed && !outcome.completed.idempotent && outcome.evidence) {
|
|
219
|
+
assertReviewProjection(options.receipt.result);
|
|
220
|
+
const { directory, file } = this.archivePath(query.effectId);
|
|
221
|
+
await mkdir(directory, { recursive: true, mode: 0o700 });
|
|
222
|
+
const archived = { schema: ARCHIVE_SCHEMA, effectId: query.effectId, recordHash: outcome.completed.recordHash,
|
|
223
|
+
receipt: structuredClone(options.receipt), evidence: outcome.evidence };
|
|
224
|
+
await publishExclusive(directory, file, `${canonicalJson(archived)}\n`, 'receipt');
|
|
225
|
+
}
|
|
226
|
+
const receipt = await this.completedReceipt(query);
|
|
227
|
+
return receipt ? { result: 'present', receipt } : unknown;
|
|
228
|
+
}
|
|
229
|
+
/** Called by the executor after the effect: verify, then record `completed` only on `present`. */
|
|
230
|
+
async recordCompleted(query, receipt) {
|
|
231
|
+
if (receipt.effectId !== query.effectId)
|
|
232
|
+
throw new Error('Invalid executor completion receipt');
|
|
233
|
+
assertReviewProjection(receipt.result);
|
|
234
|
+
return (await this.reconcileReceipt(query, { receipt })).result === 'present';
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Import receipts from the deprecated HMAC journal as `completed` records.
|
|
238
|
+
* Each is verified by a one-shot `legacy-hmac` verifier over the legacy
|
|
239
|
+
* journal (evidence `method: legacy-hmac`), and its body is archived so the
|
|
240
|
+
* same receipt is returned afterwards. Receipts already completed are skipped.
|
|
241
|
+
*/
|
|
242
|
+
async importLegacyReceipts(legacy) {
|
|
243
|
+
let imported = 0;
|
|
244
|
+
let skipped = 0;
|
|
245
|
+
for (const { query, receipt } of await legacy.listReceipts()) {
|
|
246
|
+
if (!this.inScope(query) || await this.completedReceipt(query)) {
|
|
247
|
+
skipped += 1;
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
const identity = { tenantId: query.tenantId, projectId: query.projectId, reviewId: query.reviewId,
|
|
251
|
+
continuationId: receipt.continuationId, proposalVersion: receipt.proposalVersion };
|
|
252
|
+
if (reviewEffectId(identity) !== query.effectId) {
|
|
253
|
+
skipped += 1;
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
await this.recordIntent(identity, reviewDigest({ legacyReceipt: reviewDigest(receipt) }));
|
|
257
|
+
const importer = new LedgerReviewEffectJournal(this.ledger, {
|
|
258
|
+
...this.options, verifiers: createVerifierRegistry([legacyHmacImportVerifier(legacy)]),
|
|
259
|
+
});
|
|
260
|
+
const settled = await importer.reconcileReceipt(query, { receipt });
|
|
261
|
+
if (settled.result === 'present')
|
|
262
|
+
imported += 1;
|
|
263
|
+
else
|
|
264
|
+
skipped += 1;
|
|
265
|
+
}
|
|
266
|
+
return { imported, skipped };
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/** One-shot importer verifier: `present` only when the legacy HMAC journal holds the exact expected receipt. */
|
|
270
|
+
function legacyHmacImportVerifier(legacy) {
|
|
271
|
+
return {
|
|
272
|
+
kind: REVIEW_EFFECT_KIND, version: LEGACY_HMAC_IMPORT_VERIFIER_VERSION, canReportAbsent: false,
|
|
273
|
+
async verify(request) {
|
|
274
|
+
const { reviewId } = request.context;
|
|
275
|
+
const expected = request.expected.digest ?? null;
|
|
276
|
+
const found = await legacy.completedReceipt({ tenantId: request.scope.tenant, projectId: request.scope.project,
|
|
277
|
+
reviewId: String(reviewId), effectId: request.effectId }).catch(() => null);
|
|
278
|
+
const actual = found ? reviewDigest(found) : null;
|
|
279
|
+
const evidence = { source: 'd13-legacy-journal', method: 'legacy-hmac', expectedDigest: expected, receiptDigest: actual };
|
|
280
|
+
return actual && actual === expected
|
|
281
|
+
? { result: 'present', reason: 'digest-match', complete: true, evidence }
|
|
282
|
+
: { result: 'unknown', reason: 'evidence-conflict', complete: false, evidence };
|
|
283
|
+
},
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
/** Factory form of the ledger-backed `VerifiedReviewEffectLedger`. */
|
|
287
|
+
export function ledgerVerifiedReviewEffectLedger(ledger, options = {}) {
|
|
288
|
+
return new LedgerReviewEffectJournal(ledger, options);
|
|
289
|
+
}
|
|
290
|
+
/** Production wiring: a `review` ledger with the built-in verifiers and the review-store verifier. */
|
|
291
|
+
export function openReviewEffectLedger(options) {
|
|
292
|
+
const verifiers = createBuiltinVerifierRegistry({ review: { store: options.store, execution: options.execution } }, options.extensions ?? []);
|
|
293
|
+
const ledger = new EffectLedger({
|
|
294
|
+
projectDir: options.projectDir, scope: { tenant: options.tenantId, project: options.projectId, subsystem: 'review' },
|
|
295
|
+
writer: options.writer, keyProvider: options.keyProvider, verifiers, sink: options.sink, clock: options.clock,
|
|
296
|
+
});
|
|
297
|
+
return new LedgerReviewEffectJournal(ledger, { verifiers, independentOf: options.independentOf, timeoutMs: options.timeoutMs });
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Production `reconcile(effectId)` for `DecisionReviewService.resume`. It
|
|
301
|
+
* returns a receipt only for a signed `completed` record or a `present`
|
|
302
|
+
* verifier result under the exact D13 identity; `absent` and `unknown` return
|
|
303
|
+
* `null`, so the stale continuation stays uncertain and is never replayed.
|
|
304
|
+
*/
|
|
305
|
+
export function ledgerReviewReconciler(input) {
|
|
306
|
+
return async (effectId) => {
|
|
307
|
+
if (effectId !== reviewDigest({ reviewId: input.reviewId, continuationId: input.continuationId, proposalVersion: input.proposalVersion }))
|
|
308
|
+
return null;
|
|
309
|
+
const query = { tenantId: input.scope.tenantId, projectId: input.scope.projectId, reviewId: input.reviewId, effectId };
|
|
310
|
+
const known = await input.journal.completedReceipt(query);
|
|
311
|
+
if (known)
|
|
312
|
+
return known.effectId === effectId ? known : null;
|
|
313
|
+
if (!input.journal.reconcileReceipt)
|
|
314
|
+
return null;
|
|
315
|
+
const settled = await input.journal.reconcileReceipt(query, {
|
|
316
|
+
identity: { continuationId: input.continuationId, proposalVersion: input.proposalVersion }, actionDigest: input.actionDigest,
|
|
317
|
+
});
|
|
318
|
+
return settled.result === 'present' && settled.receipt?.effectId === effectId ? settled.receipt : null;
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
//# sourceMappingURL=effect-ledger-adapter.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from './types.js';
|
|
2
|
+
export * from './validate.js';
|
|
3
|
+
export * from './store.js';
|
|
4
|
+
export * from './service.js';
|
|
5
|
+
export * from './migrate.js';
|
|
6
|
+
export * from './recovery.js';
|
|
7
|
+
export * from './recovery-adapters.js';
|
|
8
|
+
export * from './effect-ledger-adapter.js';
|
|
9
|
+
export * from './authorization.js';
|
|
10
|
+
export * from './operator-audit.js';
|
|
11
|
+
export { JsonlOperatorDecisionStore, verifyDecisionChain } from '../../audit/operator-decision.js';
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { reviewDigest } from './validate.js';
|
|
2
|
+
/** Explicit opt-in bridge; receiving a review result never creates durable state. */
|
|
3
|
+
export function durableReviewInputFromRuleset(result, options) {
|
|
4
|
+
if (!options.enabled)
|
|
5
|
+
return null;
|
|
6
|
+
if (result.spec.status !== 'review')
|
|
7
|
+
throw new Error('Only a review RulesetResult may enter durable review');
|
|
8
|
+
if (result.spec.outcome === undefined)
|
|
9
|
+
throw new Error('Durable review requires a proposed outcome');
|
|
10
|
+
if (!options.reviewId || !options.continuationId || !options.resumeToken || !options.rationale
|
|
11
|
+
|| !options.riskTier || !options.reasonCodes.length) {
|
|
12
|
+
throw new Error('Durable review migration requires explicit identity, rationale, risk, reason, and continuation data');
|
|
13
|
+
}
|
|
14
|
+
const evidencePins = Object.values(result.spec.evaluations)
|
|
15
|
+
.map(evaluation => evaluation.spec.decision)
|
|
16
|
+
.sort((left, right) => left.id.localeCompare(right.id) || left.version.localeCompare(right.version));
|
|
17
|
+
return {
|
|
18
|
+
reviewId: options.reviewId,
|
|
19
|
+
sourceReceipt: { id: result.spec.invocationId, digest: reviewDigest(result) },
|
|
20
|
+
evidencePins,
|
|
21
|
+
policyPins: [result.spec.ruleset, result.spec.binding],
|
|
22
|
+
reasonCodes: [...options.reasonCodes],
|
|
23
|
+
riskTier: options.riskTier,
|
|
24
|
+
presentation: structuredClone(options.presentation ?? {}),
|
|
25
|
+
action: structuredClone(result.spec.outcome),
|
|
26
|
+
rationale: options.rationale,
|
|
27
|
+
expiresAtEpochMs: options.expiresAtEpochMs,
|
|
28
|
+
...(options.escalationAtEpochMs === undefined ? {} : { escalationAtEpochMs: options.escalationAtEpochMs }),
|
|
29
|
+
...(options.quorum === undefined ? {} : { quorum: options.quorum }),
|
|
30
|
+
continuationId: options.continuationId,
|
|
31
|
+
resumeToken: options.resumeToken,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=migrate.js.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { digestDecisionContext, verifyDecisionChain, } from '../../audit/operator-decision.js';
|
|
2
|
+
import { canonicalJson } from '../../security/artifact-trust.js';
|
|
3
|
+
import { ReviewIntegrityError, reviewOperatorEventId, validateReview } from './validate.js';
|
|
4
|
+
/** #1567 record mapping: review event ID is the operator audit event ID, not a parallel identity. */
|
|
5
|
+
export function reviewOperatorDecisionInput(review, event, correlation, classification) {
|
|
6
|
+
const outcome = { approved: ['approval', 'approved'], rejected: ['denial', 'denied'],
|
|
7
|
+
escalated: ['escalation', 'escalated'], 'authorization-denied': ['denial', 'denied'] }[event.type];
|
|
8
|
+
if (!outcome)
|
|
9
|
+
return null;
|
|
10
|
+
if (event.operatorDecisionEventId && event.operatorDecisionEventId !== reviewOperatorEventId(review.reviewId, event.sequence)) {
|
|
11
|
+
throw new ReviewIntegrityError('Operator audit identity mismatch');
|
|
12
|
+
}
|
|
13
|
+
const proposal = review.proposals.find(item => item.version === event.proposalVersion);
|
|
14
|
+
if (!proposal)
|
|
15
|
+
throw new ReviewIntegrityError('Operator audit proposal mismatch');
|
|
16
|
+
return {
|
|
17
|
+
kind: outcome[0], outcome: outcome[1],
|
|
18
|
+
event_id: event.operatorDecisionEventId ?? reviewOperatorEventId(review.reviewId, event.sequence),
|
|
19
|
+
timestamp: new Date(event.atEpochMs).toISOString(),
|
|
20
|
+
actor: { id: event.actor.id, type: 'human', authentication: event.actor.authorityContext, roles: event.actor.roles },
|
|
21
|
+
reason: event.rationale, classification, correlation,
|
|
22
|
+
policy_ref: review.policyPins.map(pin => `${pin.id}@${pin.version}`).join(',') || undefined,
|
|
23
|
+
context: { reviewId: review.reviewId, sourceReceipt: review.sourceReceipt,
|
|
24
|
+
proposalVersion: event.proposalVersion, actionDigest: proposal.actionDigest },
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Recover missing #1567 audit records after a crash using the durable review
|
|
29
|
+
* event IDs. Caller must serialize writers to the operator JSONL audit store.
|
|
30
|
+
*/
|
|
31
|
+
export async function replayReviewOperatorAudit(review, store, correlation, classification) {
|
|
32
|
+
validateReview(review);
|
|
33
|
+
const prior = await store.read();
|
|
34
|
+
if (!verifyDecisionChain(prior).ok)
|
|
35
|
+
throw new ReviewIntegrityError('Operator audit chain is invalid');
|
|
36
|
+
const matched = [];
|
|
37
|
+
for (const event of review.events) {
|
|
38
|
+
const input = reviewOperatorDecisionInput(review, event, correlation, classification);
|
|
39
|
+
if (!input)
|
|
40
|
+
continue;
|
|
41
|
+
const existing = prior.find(record => record.event_id === input.event_id);
|
|
42
|
+
if (existing) {
|
|
43
|
+
if (existing.kind !== input.kind || existing.outcome !== input.outcome || existing.actor.id !== input.actor.id ||
|
|
44
|
+
existing.context_digest !== digestDecisionContext(input.context) ||
|
|
45
|
+
canonicalJson(existing.correlation) !== canonicalJson(input.correlation) ||
|
|
46
|
+
existing.reason !== input.reason || existing.policy_ref !== input.policy_ref) {
|
|
47
|
+
throw new ReviewIntegrityError('Conflicting operator audit record');
|
|
48
|
+
}
|
|
49
|
+
matched.push(existing);
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
const created = await store.append(input);
|
|
53
|
+
prior.push(created);
|
|
54
|
+
matched.push(created);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return matched;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=operator-audit.js.map
|