@aiwg/cli 2026.10.10 → 2026.10.12
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/model-capabilities.v1.json +78 -14
- package/agentic/code/providers/model-catalog.v1.json +62 -12
- package/dist/src/artifacts/binding-target-identity.js +51 -0
- package/dist/src/artifacts/checksum-manifest.js +12 -0
- package/dist/src/artifacts/index-builder.js +11 -13
- package/dist/src/artifacts/query-engine.js +5 -11
- package/dist/src/cli/handlers/doctor-help.js +32 -0
- package/dist/src/cli/handlers/gates.js +19 -18
- package/dist/src/cli/handlers/models-help.js +7 -1
- package/dist/src/cli/handlers/models.js +3 -3
- package/dist/src/cli/handlers/script-runner.js +8 -5
- package/dist/src/cli/handlers/sessions-intake.js +104 -0
- package/dist/src/cli/handlers/sessions-tail.js +172 -0
- package/dist/src/cli/handlers/sessions.js +287 -31
- package/dist/src/cli/handlers/skill-lint.js +10 -3
- package/dist/src/cli/handlers/steward-format.js +100 -0
- package/dist/src/cli/handlers/steward-models.js +167 -0
- package/dist/src/cli/handlers/steward.js +14 -248
- package/dist/src/cli/handlers/use-bundle-counts.js +61 -0
- package/dist/src/cli/handlers/use.js +18 -28
- package/dist/src/cli/handlers/utilities.js +1 -24
- package/dist/src/config/aiwg-config.js +7 -0
- package/dist/src/config/authorization-cli.js +89 -0
- package/dist/src/config/cli-help.js +1 -0
- package/dist/src/config/cli.js +10 -1
- package/dist/src/config/worker-context.js +337 -0
- package/dist/src/decision/context-pruning.js +3 -2
- package/dist/src/decision/ensemble/contract.js +15 -4
- package/dist/src/decision/ensemble-study/artifacts.js +5 -0
- package/dist/src/decision/ensemble-study/score.js +4 -3
- package/dist/src/decision/feature-export.js +3 -9
- package/dist/src/decision/heldout/collector.js +2 -2
- package/dist/src/decision/issue-triage/runtime.js +3 -9
- package/dist/src/decision/qualification/gates.js +2 -1
- package/dist/src/decision/qualification/release.js +2 -3
- package/dist/src/decision/routing/report.js +2 -0
- package/dist/src/decision/sdlc-screening.js +3 -5
- package/dist/src/decision/sensitivity/comparative.js +5 -3
- package/dist/src/gates/driver.js +6 -77
- package/dist/src/gates/evaluate.js +4 -11
- package/dist/src/gates/floors.js +4 -4
- package/dist/src/gates/index.js +3 -3
- package/dist/src/gates/lattice.js +26 -0
- package/dist/src/gates/registry.js +2 -1
- package/dist/src/gates/report.js +19 -7
- package/dist/src/issues/audit-cli.js +120 -0
- package/dist/src/issues/audit-contract.js +64 -0
- package/dist/src/issues/audit-local.js +82 -0
- package/dist/src/issues/audit.js +220 -0
- package/dist/src/lint/rule-activation.js +15 -1
- package/dist/src/mcp/tools/worker-invoke.mjs +108 -10
- package/dist/src/mission-protocol/canonical-codecs.js +270 -0
- package/dist/src/mission-protocol/codecs.js +22 -550
- package/dist/src/mission-protocol/health-trace-codecs.js +281 -0
- package/dist/src/mission-protocol/health-trace-types.js +43 -0
- package/dist/src/mission-protocol/types.js +2 -42
- package/dist/src/mission-protocol/worker-contract-json.js +10 -0
- package/dist/src/mission-protocol/worker-review-gate.js +100 -0
- package/dist/src/mission-protocol/worker-review-types.js +2 -0
- package/dist/src/models/catalog-policy.js +280 -0
- package/dist/src/models/index.js +1 -1
- package/dist/src/models/model-capabilities.v1.json +78 -14
- package/dist/src/models/model-catalog.v1.json +62 -12
- package/dist/src/models/policy-resources.js +28 -0
- package/dist/src/models/provider-policy.js +10 -539
- package/dist/src/models/resolver.js +26 -8
- package/dist/src/models/router.js +15 -2
- package/dist/src/models/tiers.js +13 -0
- package/dist/src/models/types.js +0 -6
- package/dist/src/models/worker-ceilings.js +59 -0
- package/dist/src/models/worker-invocation-policy.js +243 -0
- package/dist/src/models/worker-plan.js +16 -20
- package/dist/src/models/worker-review-lineage.js +55 -0
- package/dist/src/models/worker-selection-policy.js +1 -1
- package/dist/src/models/wrapper-route.js +6 -3
- package/dist/src/policy/authorization.js +30 -1
- package/dist/src/policy/hitl-config.js +63 -0
- package/dist/src/policy/hitl-receipt.js +57 -0
- package/dist/src/policy/hitl-types.js +4 -0
- package/dist/src/policy/hitl-validation.js +39 -0
- package/dist/src/policy/hitl.js +117 -0
- package/dist/src/process/aiwg-probe-command.d.mts +15 -0
- package/dist/src/process/aiwg-probe-command.mjs +37 -0
- package/dist/src/rules/activation.d.mts +1 -1
- package/dist/src/rules/activation.mjs +0 -2
- package/dist/src/rules/gate-coverage.js +51 -0
- package/dist/src/rules/gate-references.js +16 -0
- package/dist/src/rules/rule-frontmatter.schema.json +7 -1
- package/dist/src/serve/worker-command-request.js +21 -4
- package/dist/src/serve/worker-invocation-preparation.js +3 -1
- package/dist/src/serve/worker-invocation.js +21 -10
- package/dist/src/sessions/adapters/deepseek-harness.js +1 -0
- package/dist/src/sessions/adapters/grok-build-native.js +269 -0
- package/dist/src/sessions/adapters/grok-build.js +57 -15
- package/dist/src/sessions/adapters/grokbot.js +7 -0
- package/dist/src/sessions/adapters/hermes-state-db.js +252 -0
- package/dist/src/sessions/adapters/hermes.js +42 -3
- package/dist/src/sessions/adapters/warp.js +1 -1
- package/dist/src/sessions/batch-import.js +44 -6
- package/dist/src/sessions/contracts.js +20 -1
- package/dist/src/sessions/coverage-preflight.js +7 -1
- package/dist/src/sessions/discovery-capability.js +38 -0
- package/dist/src/sessions/fortemi-export-mapping.js +33 -3
- package/dist/src/sessions/host-catalog.js +157 -0
- package/dist/src/sessions/image-blobs.js +141 -0
- package/dist/src/sessions/importer.js +9 -232
- package/dist/src/sessions/index.js +6 -0
- package/dist/src/sessions/intake-dir.js +71 -0
- package/dist/src/sessions/normalization.js +251 -0
- package/dist/src/sessions/refresh.js +25 -2
- package/dist/src/sessions/repository.js +164 -22
- package/dist/src/sessions/tail.js +91 -0
- package/dist/src/sessions/workspace-discovery.js +88 -9
- package/dist/src/skills/agent-skill-sidecar.js +28 -0
- package/dist/src/skills/agent-skills.js +4 -23
- package/dist/src/skills/claude-surface-projection.js +25 -0
- package/dist/src/skills/deployer.js +8 -288
- package/dist/src/skills/deployment-policy.js +114 -0
- package/dist/src/skills/deployment-projection.js +152 -0
- package/dist/src/skills/deployment-types.js +20 -0
- package/dist/src/skills/exporter.js +8 -31
- package/dist/src/skills/provider-extensions.js +41 -0
- package/dist/src/skills/run.js +6 -0
- package/dist/src/skills/types.js +0 -8
- package/dist/src/skills/validator-resources.js +68 -0
- package/dist/src/skills/validator.js +6 -55
- package/dist/src/storage/cli-migrate-options.js +125 -0
- package/dist/src/storage/cli-migrate.js +111 -0
- package/dist/src/storage/cli.js +4 -279
- package/dist/src/storage/migration-adapter.js +79 -0
- package/dist/src/storage/migration-copy.js +108 -0
- package/dist/src/storage/migration-protocol.js +14 -197
- package/dist/src/storage/migration-records.js +186 -0
- package/dist/src/storage/migration-types.js +12 -0
- package/dist/src/tracker/forge-http.js +2 -1
- package/dist/src/tracker/issue-pages.js +110 -0
- package/package.json +7 -1
- package/schemas/mission-v1.schema.json +335 -0
- package/schemas/models/model-catalog.v1.schema.json +19 -1
- package/schemas/models/model-config-compatibility.v1.schema.json +3 -2
- package/schemas/models/model-policy.v1.schema.json +1 -1
- package/tools/agents/providers/provider-paths-staging.mjs +35 -0
- package/tools/agents/providers/rule-binding-targets.mjs +146 -0
- package/tools/agents/providers/rule-delivery.mjs +7 -126
- package/tools/ralph-external/lib/muse-adapter.mjs +10 -0
- package/tools/ralph-external/lib/worker-context.mjs +237 -0
- package/tools/ralph-external/lib/worker-invocation-ancestry.mjs +132 -0
- package/tools/ralph-external/lib/worker-invocation-processes.mjs +123 -0
- package/tools/ralph-external/lib/worker-isolation-status.mjs +48 -0
- package/tools/ralph-external/lib/worker-isolation.mjs +17 -29
- package/tools/ralph-external/lib/worker-launch-cli.mjs +3 -1
- package/tools/ralph-external/lib/worker-overflow.mjs +48 -13
- package/tools/ralph-external/lib/worker-retry-evidence.mjs +103 -0
- package/tools/ralph-external/lib/worker-termination.mjs +82 -0
- package/tools/ralph-external/session-launcher.mjs +51 -96
- package/vscode-extension/schemas/aiwg.config.v1.json +177 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertDecisionCeiling, ceilingDecision, GATE_RANK, GateDecisionUpgradeError } from '../../gates/lattice.js';
|
|
1
2
|
import { createHash } from 'node:crypto';
|
|
2
3
|
import { existsSync, readFileSync } from 'node:fs';
|
|
3
4
|
import { dirname, resolve } from 'node:path';
|
|
@@ -10,8 +11,8 @@ import { admitEntry } from '../entry.js';
|
|
|
10
11
|
export class EnsembleContractError extends Error {
|
|
11
12
|
layer;
|
|
12
13
|
details;
|
|
13
|
-
constructor(message, layer = 'semantic', details = []) {
|
|
14
|
-
super(message);
|
|
14
|
+
constructor(message, layer = 'semantic', details = [], cause) {
|
|
15
|
+
super(message, { cause });
|
|
15
16
|
this.layer = layer;
|
|
16
17
|
this.details = details;
|
|
17
18
|
this.name = 'EnsembleContractError';
|
|
@@ -41,7 +42,6 @@ export const ENSEMBLE_DISAGREEMENT_PRIMITIVES = {
|
|
|
41
42
|
const UNCERTAINTY_COMBINING = new Set(['mean-probability-v1', 'jensen-shannon-v1']);
|
|
42
43
|
export const REQUIRED_PAIRED_METRICS = ['quality', 'calibration', 'risk-coverage', 'abstention', 'latency', 'tokens', 'cost', 'slice'];
|
|
43
44
|
const CEILING_FIELDS = ['members', 'attempts', 'deadlineMs', 'tokens', 'costMicros', 'concurrency', 'fallbackDepth'];
|
|
44
|
-
const GATE_RANK = { PROMOTE: 0, HOLD: 1, ROLLBACK: 2 };
|
|
45
45
|
let validators = null;
|
|
46
46
|
function schemaValidator(kind) {
|
|
47
47
|
if (!validators) {
|
|
@@ -334,7 +334,7 @@ export function pairedDeltaFinding(threshold, item) {
|
|
|
334
334
|
export function ensembleIntegrityDecision(integrity, findingCount) {
|
|
335
335
|
const upstream = integrity.release_gate.decision;
|
|
336
336
|
const compromised = integrity.integrity_state === 'compromised' || integrity.compromise_labels.length > 0;
|
|
337
|
-
return upstream
|
|
337
|
+
return ceilingDecision(upstream, compromised ? 'ROLLBACK' : 'PROMOTE', findingCount > 0 ? 'HOLD' : 'PROMOTE');
|
|
338
338
|
}
|
|
339
339
|
/** A D17 report may keep or tighten the upstream #2037/#2048 decision, never loosen it. Every
|
|
340
340
|
* finding, passed flag and the decision are recomputed from the carried integrity fields and the
|
|
@@ -346,6 +346,15 @@ export function validateEnsembleIntegrityReport(value) {
|
|
|
346
346
|
const problems = [];
|
|
347
347
|
if (report.upstreamDecision !== report.integrity.release_gate.decision)
|
|
348
348
|
problems.push('upstream decision must be the eval-integrity release gate decision');
|
|
349
|
+
let upgrade;
|
|
350
|
+
try {
|
|
351
|
+
assertDecisionCeiling(report.decision, report.upstreamDecision, ensembleIntegrityDecision(report.integrity, report.findings.length));
|
|
352
|
+
}
|
|
353
|
+
catch (error) {
|
|
354
|
+
if (!(error instanceof GateDecisionUpgradeError))
|
|
355
|
+
throw error;
|
|
356
|
+
upgrade = error;
|
|
357
|
+
}
|
|
349
358
|
if (GATE_RANK[report.decision] < GATE_RANK[report.upstreamDecision])
|
|
350
359
|
problems.push(`D17 cannot upgrade ${report.upstreamDecision} to ${report.decision}`);
|
|
351
360
|
if (report.decision === 'PROMOTE' && (report.findings.length > 0 || report.pairedDeltas.some(item => !item.passed)))
|
|
@@ -372,6 +381,8 @@ export function validateEnsembleIntegrityReport(value) {
|
|
|
372
381
|
problems.push(`decision ${report.decision} does not follow from its integrity and findings (${decision})`);
|
|
373
382
|
if (digest !== ensembleContractDigest(payload))
|
|
374
383
|
problems.push('integrity report digest does not match its content');
|
|
384
|
+
if (upgrade)
|
|
385
|
+
throw new EnsembleContractError('ensemble integrity report rejected', 'semantic', problems, upgrade);
|
|
375
386
|
semantic(problems, 'ensemble integrity report rejected');
|
|
376
387
|
return report;
|
|
377
388
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertDecisionCeiling, ceilingDecision } from '../../gates/lattice.js';
|
|
1
2
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
3
|
import { dirname, resolve } from 'node:path';
|
|
3
4
|
import { fileURLToPath } from 'node:url';
|
|
@@ -32,6 +33,10 @@ export function validateD17Artifact(name, value) {
|
|
|
32
33
|
admitEntry(value, name === 'report' || name === 'reportV2' ? reportLimits : limits);
|
|
33
34
|
if (!validators[name]?.(value))
|
|
34
35
|
throw new Error(`D17 invalid ${name} artifact`);
|
|
36
|
+
if (name === 'report' || name === 'reportV2') {
|
|
37
|
+
const report = value;
|
|
38
|
+
assertDecisionCeiling(report.decision, ceilingDecision('HOLD', report.integrity.release_gate.decision, report.integrity.compromise_labels.length ? 'ROLLBACK' : 'PROMOTE'));
|
|
39
|
+
}
|
|
35
40
|
if (name === 'splits') {
|
|
36
41
|
const manifest = value;
|
|
37
42
|
const ids = new Set(), inputs = new Set(), families = new Map();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ceilingDecision } from '../../gates/lattice.js';
|
|
1
2
|
import { heldoutDigest, validateHeldoutAttempt, validateHeldoutInputs } from '../heldout/contract.js';
|
|
2
3
|
import { artifactPin } from '../validate.js';
|
|
3
4
|
import { aggregateEnsembleResults } from '../ensemble/aggregate.js';
|
|
@@ -145,7 +146,7 @@ export async function scoreD17Study(input, prepared) {
|
|
|
145
146
|
calibrationDiagnostics: { champion: binary('champion'), challenger: binary('challenger'), calibrated: false },
|
|
146
147
|
pending: ['D09 compatible member and aggregate calibration', 'blind gold audit and delayed repeats', 'separate extra-cost tradeoff approval',
|
|
147
148
|
'protected native report approval and eligibility'],
|
|
148
|
-
decision: input.integrity.release_gate.decision
|
|
149
|
+
decision: ceilingDecision('HOLD', input.integrity.release_gate.decision, input.integrity.compromise_labels.length ? 'ROLLBACK' : 'PROMOTE') };
|
|
149
150
|
validateD17Artifact('report', report);
|
|
150
151
|
return report;
|
|
151
152
|
}
|
|
@@ -325,7 +326,7 @@ export async function scoreD17StagedStudy(input, prepared, context) {
|
|
|
325
326
|
mappingDigest: heldoutDigest(verified[role].mapping), compatibility: { action: 'allow',
|
|
326
327
|
state: verified[role].compatibility.state, pinId: verified[role].compatibility.pinId } });
|
|
327
328
|
const approved = input.approvedCalibration;
|
|
328
|
-
const decision = input.integrity.release_gate.decision
|
|
329
|
+
const decision = ceilingDecision('HOLD', input.integrity.release_gate.decision, input.integrity.compromise_labels.length ? 'ROLLBACK' : 'PROMOTE');
|
|
329
330
|
const report = { schemaVersion: 'decision-d17-study-report/v2', scope: 'synthetic-staged-calibrated',
|
|
330
331
|
calibrated: true, d09Qualified: true, calibratedGate: true,
|
|
331
332
|
corpusDigest: heldoutDigest(prepared.corpus), preregistrationDigest: heldoutDigest(prepared.preregistration),
|
|
@@ -453,7 +454,7 @@ export function d17NativeIntegrity(input) {
|
|
|
453
454
|
// The ensemble integrity contract carries only the gate decision and reasons (eval-tool thresholds are dropped).
|
|
454
455
|
const gate = { decision: input.base.release_gate.decision, reasons: [...input.base.release_gate.reasons] };
|
|
455
456
|
const release_gate = gate.decision === 'PROMOTE' && missing.length
|
|
456
|
-
? { decision: 'HOLD', reasons: [...gate.reasons, ...missing.map(reason => `promotion withheld: ${reason}`)] } : gate;
|
|
457
|
+
? { decision: ceilingDecision(gate.decision, 'HOLD'), reasons: [...gate.reasons, ...missing.map(reason => `promotion withheld: ${reason}`)] } : gate;
|
|
457
458
|
const { sample_n, uncertainty, integrity_mode, fresh_workspace_required, fresh_workspace_verified, integrity_state, trusted_score_source, compromise_labels, weak_signal_reason } = input.base;
|
|
458
459
|
return { sample_n, uncertainty, paired_baseline: { ...input.pairedBaseline, d17TradeoffApprovalDigest: tradeoffDigest },
|
|
459
460
|
integrity_mode, fresh_workspace_required, fresh_workspace_verified, integrity_state, trusted_score_source,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertDecisionCeiling } from '../gates/lattice.js';
|
|
1
2
|
import { createHash } from 'node:crypto';
|
|
2
3
|
import { existsSync, readFileSync } from 'node:fs';
|
|
3
4
|
import { dirname, resolve } from 'node:path';
|
|
@@ -942,9 +943,7 @@ function normalizeEvalIntegrity(value) {
|
|
|
942
943
|
if (value.upstreamDisposition !== value.qualificationRelease.decision) {
|
|
943
944
|
throw new DecisionFeatureExportError('Feature export integrity must preserve the upstream qualification release decision');
|
|
944
945
|
}
|
|
945
|
-
|
|
946
|
-
throw new DecisionFeatureExportError(`Feature export integrity cannot upgrade ${value.upstreamDisposition} to ${value.disposition}`);
|
|
947
|
-
}
|
|
946
|
+
assertDecisionCeiling(value.disposition, value.upstreamDisposition);
|
|
948
947
|
const { digest, ...payload } = value;
|
|
949
948
|
if (digest !== digestEvalIntegrity(payload))
|
|
950
949
|
throw new DecisionFeatureExportError('Feature export integrity digest does not match its content');
|
|
@@ -966,9 +965,7 @@ function validateQualificationRelease(record) {
|
|
|
966
965
|
throw new DecisionFeatureExportError('Feature export qualification release digest does not match its content');
|
|
967
966
|
}
|
|
968
967
|
validateQualificationIntegrity(record.integrity);
|
|
969
|
-
|
|
970
|
-
throw new DecisionFeatureExportError('Feature export qualification release cannot upgrade its integrity release gate');
|
|
971
|
-
}
|
|
968
|
+
assertDecisionCeiling(record.decision, record.integrity.release_gate.decision, record.integrity.integrity_state === 'compromised' || record.integrity.compromise_labels.length ? 'ROLLBACK' : 'PROMOTE', ...record.gates.map(gate => gate.status === 'pass' ? 'PROMOTE' : 'HOLD'));
|
|
972
969
|
}
|
|
973
970
|
function validateQualificationIntegrity(value) {
|
|
974
971
|
if (!value || !Number.isSafeInteger(value.sample_n) || value.sample_n < 0
|
|
@@ -986,9 +983,6 @@ function validateQualificationIntegrity(value) {
|
|
|
986
983
|
function digestEvalIntegrity(value) {
|
|
987
984
|
return `sha256:${createHash('sha256').update(canonicalJson(value)).digest('hex')}`;
|
|
988
985
|
}
|
|
989
|
-
function integrityRank(decision) {
|
|
990
|
-
return { PROMOTE: 0, HOLD: 1, ROLLBACK: 2 }[decision];
|
|
991
|
-
}
|
|
992
986
|
function activeExportHold(hold, subject, nowEpochMs) {
|
|
993
987
|
return hold.subject === subject && hold.expiresAt > nowEpochMs && hold.scope.includes('export');
|
|
994
988
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ceilingDecision } from '../../gates/lattice.js';
|
|
1
2
|
import { createHash } from 'node:crypto';
|
|
2
3
|
import { mkdir, readFile, rm } from 'node:fs/promises';
|
|
3
4
|
import { join, resolve } from 'node:path';
|
|
@@ -425,7 +426,6 @@ export async function scoreHeldoutStudy(input) {
|
|
|
425
426
|
return { schemaVersion: 'decision-heldout-score/v1', source: frozen.priorRuns.some(run => run.source === 'injected-transport') ? 'injected-transport' : frozen.source, evidenceDigest: input.trustedEvidenceDigest, integrityDigest: input.trustedIntegrityDigest,
|
|
426
427
|
complete, diagnostics, approvedCalibration, calibrated: studyValidated ? true : calibrated, d09Qualified: studyValidated,
|
|
427
428
|
calibratedGate: studyValidated && study?.calibratedGate === true,
|
|
428
|
-
calibrationArtifactValidation: studyValidated ? 'performed-by-study-scorer' : 'not-performed', integrityProblems: problems, decision: input.integrity.release_gate.decision
|
|
429
|
-
|| input.integrity.compromise_labels.length > 0 ? 'ROLLBACK' : 'HOLD' };
|
|
429
|
+
calibrationArtifactValidation: studyValidated ? 'performed-by-study-scorer' : 'not-performed', integrityProblems: problems, decision: ceilingDecision('HOLD', input.integrity.release_gate.decision, input.integrity.compromise_labels.length > 0 ? 'ROLLBACK' : 'PROMOTE') };
|
|
430
430
|
}
|
|
431
431
|
//# sourceMappingURL=collector.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertDecisionCeiling, ceilingDecision } from '../../gates/lattice.js';
|
|
1
2
|
import { createHash } from 'node:crypto';
|
|
2
3
|
import { existsSync, readFileSync } from 'node:fs';
|
|
3
4
|
import { dirname, resolve } from 'node:path';
|
|
@@ -26,7 +27,6 @@ const SCHEMA_FILES = {
|
|
|
26
27
|
manifest: 'DecisionIssueTriageEvaluationManifest.v1.schema.json',
|
|
27
28
|
report: 'DecisionIssueTriageEvaluationReport.v1.schema.json',
|
|
28
29
|
};
|
|
29
|
-
const GATE_RANK = { PROMOTE: 0, HOLD: 1, ROLLBACK: 2 };
|
|
30
30
|
const FINAL_FIELDS = ['finalLabels', 'finalDuplicateOf', 'resolution', 'closedAt'];
|
|
31
31
|
const RESPONSE_FIELDS = ['issueId', 'issueType', 'area', 'urgency', 'completeness', 'clarificationNeed', 'duplicate',
|
|
32
32
|
'uncertaintyProfile', 'accepted', 'actualModel', 'requestedModel', 'latencyMs', 'usage', 'usageReceipts', 'calls', 'retries', 'fallbacks',
|
|
@@ -156,9 +156,7 @@ function checkIssueTriageEvaluationReportShape(value) {
|
|
|
156
156
|
checkSchema('report', value);
|
|
157
157
|
const report = value;
|
|
158
158
|
const problems = [];
|
|
159
|
-
|
|
160
|
-
problems.push(`issue triage report cannot upgrade ${report.integrity.upstreamDecision} to ${report.decision}`);
|
|
161
|
-
}
|
|
159
|
+
assertDecisionCeiling(report.decision, report.integrity.upstreamDecision, gateDecision(report.integrity.upstreamDecision, report.integrity.findings));
|
|
162
160
|
if (report.decision === 'PROMOTE' && report.integrity.findings.length > 0)
|
|
163
161
|
problems.push('PROMOTE requires no findings');
|
|
164
162
|
if (report.decision === 'PROMOTE' && report.baselineComparison.qualityNonInferiority?.decision !== 'non-inferior') {
|
|
@@ -603,11 +601,7 @@ function correct(sample, which) {
|
|
|
603
601
|
&& observed.duplicate.issueId === sample.label.duplicateOf;
|
|
604
602
|
}
|
|
605
603
|
function gateDecision(upstream, findings) {
|
|
606
|
-
|
|
607
|
-
return 'ROLLBACK';
|
|
608
|
-
if (upstream === 'HOLD' || findings.length > 0)
|
|
609
|
-
return 'HOLD';
|
|
610
|
-
return 'PROMOTE';
|
|
604
|
+
return ceilingDecision(upstream, findings.length > 0 ? 'HOLD' : 'PROMOTE');
|
|
611
605
|
}
|
|
612
606
|
/** Supported interval level in bps, or null. The paired helpers accept integer levels strictly in (5000, 9999). */
|
|
613
607
|
function levelInBps(confidenceInterval) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ceilingDecision } from '../../gates/lattice.js';
|
|
1
2
|
import { expectedQualificationCaseIds, validateCaseInventory } from './manifest.js';
|
|
2
3
|
import { deriveGateEvidence, DERIVED_GATE_EVIDENCE } from './gate-evidence.js';
|
|
3
4
|
// Negative evidence always overrides a positive suite result. A release reviewer
|
|
@@ -76,7 +77,7 @@ export function evaluateQualification(manifest, definitions = DECISION_RELEASE_G
|
|
|
76
77
|
schemaVersion: 'decision-qualification-report/v1',
|
|
77
78
|
runId: manifest.runId,
|
|
78
79
|
generatedAt: manifest.generatedAt,
|
|
79
|
-
decision: mandatoryFailed ? 'HOLD' : 'PROMOTE',
|
|
80
|
+
decision: ceilingDecision('PROMOTE', mandatoryFailed ? 'HOLD' : 'PROMOTE'),
|
|
80
81
|
gates,
|
|
81
82
|
};
|
|
82
83
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ceilingDecision } from '../../gates/lattice.js';
|
|
1
2
|
import { canonicalSha256, matchEvidenceDigest } from '../../gates/stats/index.js';
|
|
2
3
|
/**
|
|
3
4
|
* #2037/#2048 integrity modes whose verified state has a trusted score source.
|
|
@@ -133,9 +134,7 @@ export function buildQualificationReleaseRecord(executed, input) {
|
|
|
133
134
|
&& suites.length === manifest.cases.length && suites.every(item => item.outcome === 'pass' && item.verified)
|
|
134
135
|
&& report.gates.length === 7 && report.gates.every(gate => gate.status === 'pass')
|
|
135
136
|
&& report.decision === 'PROMOTE';
|
|
136
|
-
const decision = releaseDecision === 'ROLLBACK'
|
|
137
|
-
|| report.decision === 'ROLLBACK' || input.integrity.integrity_state === 'compromised' || input.integrity.compromise_labels.length > 0 ? 'ROLLBACK'
|
|
138
|
-
: complete && releaseDecision === 'PROMOTE' ? 'PROMOTE' : 'HOLD';
|
|
137
|
+
const decision = ceilingDecision(releaseDecision, report.decision, complete ? 'PROMOTE' : 'HOLD', input.integrity.integrity_state === 'compromised' || input.integrity.compromise_labels.length > 0 ? 'ROLLBACK' : 'PROMOTE');
|
|
139
138
|
const fields = {
|
|
140
139
|
schemaVersion: 'decision-qualification-release/v2',
|
|
141
140
|
runId: manifest.runId, sourceCommit: manifest.sourceCommit, dirty: manifest.dirty,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertDecisionCeiling } from '../../gates/lattice.js';
|
|
1
2
|
import { canonicalJson } from '../../security/artifact-trust.js';
|
|
2
3
|
import { ensembleIntegrityDecision, integrityMetadataFindings } from '../ensemble/contract.js';
|
|
3
4
|
import { pairedBinaryDifferenceInterval, pairedNonInferiority } from '../qualification/quality.js';
|
|
@@ -20,6 +21,7 @@ export function buildRoutingShadowReport(input) {
|
|
|
20
21
|
export function validateRoutingShadowReport(value, trustedPreregistrationDigest) {
|
|
21
22
|
checkRoutingSchema('shadowReport', value);
|
|
22
23
|
const report = value;
|
|
24
|
+
assertDecisionCeiling(report.decision, ensembleIntegrityDecision(report.integrity, report.reasons.length));
|
|
23
25
|
const rebuilt = computeRoutingShadowReport({
|
|
24
26
|
id: report.id, preregistration: report.preregistration, trustedPreregistrationDigest,
|
|
25
27
|
holdoutAccessedAt: report.holdoutAccessedAt, evaluatedAt: report.evaluatedAt,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ceilingDecision } from '../gates/lattice.js';
|
|
1
2
|
import { createHash } from 'node:crypto';
|
|
2
3
|
import { DECISION_API_VERSION, DECISION_API_VERSION_STRUCTURED, } from './types.js';
|
|
3
4
|
import { reviewDigest } from './review/validate.js';
|
|
@@ -1135,11 +1136,8 @@ export function buildSdlcScreeningReleaseReport(input) {
|
|
|
1135
1136
|
const preregistered = evaluateSdlcScreeningPreregistration(input.preregistration, input.trustedPreregistrationDigest, input.heldout, input.nowEpochMs);
|
|
1136
1137
|
const integrityProblems = qualificationIntegrityAllowlistProblems(input.integrity);
|
|
1137
1138
|
const readable = !integrityProblems.includes('integrity-invalid');
|
|
1138
|
-
const compromised = readable && (input.integrity.
|
|
1139
|
-
|
|
1140
|
-
const decision = compromised ? 'ROLLBACK'
|
|
1141
|
-
: readable && integrityProblems.length === 0 && preregistered.decision === 'pass'
|
|
1142
|
-
&& input.integrity.release_gate.decision === 'PROMOTE' ? 'PROMOTE' : 'HOLD';
|
|
1139
|
+
const compromised = readable && (input.integrity.integrity_state === 'compromised' || input.integrity.compromise_labels.length > 0);
|
|
1140
|
+
const decision = ceilingDecision(readable ? input.integrity.release_gate.decision : 'HOLD', compromised ? 'ROLLBACK' : 'PROMOTE', integrityProblems.length === 0 && preregistered.decision === 'pass' ? 'PROMOTE' : 'HOLD');
|
|
1143
1141
|
const unsigned = {
|
|
1144
1142
|
schemaVersion: input.preregistration.schemaVersion === SDLC_SCREENING_PREREGISTRATION_VERSION_V2
|
|
1145
1143
|
? SDLC_SCREENING_RELEASE_VERSION_V2 : SDLC_SCREENING_RELEASE_VERSION,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertDecisionCeiling, ceilingDecision } from '../../gates/lattice.js';
|
|
1
2
|
import { pairedBinaryDifferenceInterval, wilsonScoreInterval } from '../qualification/quality.js';
|
|
2
3
|
import { qualificationIntegrityAllowlistProblems } from '../qualification/release.js';
|
|
3
4
|
import { checkSensitivitySchema, sensitivityDigest, validateSensitivityReport } from './contract.js';
|
|
@@ -128,9 +129,8 @@ export function buildComparativeReplayReport(input) {
|
|
|
128
129
|
throw new Error('operator review ingestion is not implemented');
|
|
129
130
|
findings.add('reviewer-audit-pending');
|
|
130
131
|
const upstreamDecision = input.integrity.release_gate.decision;
|
|
131
|
-
const decision = upstreamDecision
|
|
132
|
-
|| input.integrity.compromise_labels.length > 0 ? 'ROLLBACK'
|
|
133
|
-
: upstreamDecision === 'HOLD' || findings.size > 0 ? 'HOLD' : 'PROMOTE';
|
|
132
|
+
const decision = ceilingDecision(upstreamDecision, input.integrity.integrity_state === 'compromised'
|
|
133
|
+
|| input.integrity.compromise_labels.length > 0 ? 'ROLLBACK' : 'PROMOTE', findings.size > 0 ? 'HOLD' : 'PROMOTE');
|
|
134
134
|
const payload = {
|
|
135
135
|
schemaVersion: 'decision-comparative-replay-report/v1', mode: 'shadow',
|
|
136
136
|
semantics: 'associative-sensitivity-not-causal', actionAuthorization: 'not-authorized',
|
|
@@ -147,7 +147,9 @@ export function buildComparativeReplayReport(input) {
|
|
|
147
147
|
}
|
|
148
148
|
export function validateComparativeReplayReport(report, input) {
|
|
149
149
|
checkSensitivitySchema('comparativeReport', report);
|
|
150
|
+
assertDecisionCeiling(report.decision, report.upstreamDecision, input.integrity.release_gate.decision);
|
|
150
151
|
const expected = buildComparativeReplayReport(input);
|
|
152
|
+
assertDecisionCeiling(report.decision, expected.decision);
|
|
151
153
|
if (sensitivityDigest(report) !== sensitivityDigest(expected))
|
|
152
154
|
throw new Error('comparative replay report differs from bound inputs');
|
|
153
155
|
}
|
package/dist/src/gates/driver.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { assertGateReportDecision } from './report.js';
|
|
1
2
|
import { lstatSync } from 'node:fs';
|
|
2
3
|
import { readFile } from 'node:fs/promises';
|
|
3
4
|
import path from 'node:path';
|
|
@@ -159,11 +160,12 @@ export async function validateGateFile(kind, filePath, options = {}) {
|
|
|
159
160
|
else {
|
|
160
161
|
// Full report validation needs the trusted evaluation context
|
|
161
162
|
// (`validateGateReport` re-derives the report byte-identically).
|
|
162
|
-
// `validate` checks the closed schema,
|
|
163
|
+
// `validate` checks component ceilings, the closed schema, kind and self-digest,
|
|
163
164
|
// so rewritten evidence with a stale digest still fails here.
|
|
164
165
|
const report = validateGateDocument(document);
|
|
165
166
|
if (!isRecord(document) || document.kind !== 'GateReport')
|
|
166
167
|
throw new Error('expected kind GateReport');
|
|
168
|
+
assertGateReportDecision(report);
|
|
167
169
|
const { digest, ...fields } = report;
|
|
168
170
|
if (typeof digest !== 'string' || artifactDigest(fields) !== digest) {
|
|
169
171
|
throw new Error('report digest does not match its content');
|
|
@@ -276,83 +278,10 @@ export function listGatePacks(options = {}) {
|
|
|
276
278
|
return summaries.filter(entry => entry.namespace === options.namespace);
|
|
277
279
|
return summaries;
|
|
278
280
|
}
|
|
279
|
-
/**
|
|
280
|
-
* Packs referenced by one rule's `enforcedBy` frontmatter (#2830 stub; full
|
|
281
|
-
* coverage is #2839). Scans bundled and project-local `rules/*.md` for an
|
|
282
|
-
* `enforcedBy: [aiwg:<bundle>/<pack>#<gate>]` list naming the rule. Returns
|
|
283
|
-
* the referenced pack ids (without gate fragments). Empty when no rule
|
|
284
|
-
* declares `enforcedBy` — rules remain guidance until they name a check.
|
|
285
|
-
*/
|
|
281
|
+
/** List the distinct packs named by validated rule coverage declarations. */
|
|
286
282
|
export async function packsReferencedByRule(ruleId, options = {}) {
|
|
287
|
-
const {
|
|
288
|
-
|
|
289
|
-
const root = rootDir(options);
|
|
290
|
-
for (const sub of ['frameworks', 'addons', 'extensions']) {
|
|
291
|
-
roots.push(path.join(root, 'agentic/code', sub));
|
|
292
|
-
}
|
|
293
|
-
roots.push(path.join(baseDir(options), '.aiwg'));
|
|
294
|
-
const ruleFiles = [];
|
|
295
|
-
const walk = async (dir, depth) => {
|
|
296
|
-
if (depth > 5 || ruleFiles.length > 500)
|
|
297
|
-
return;
|
|
298
|
-
let entries;
|
|
299
|
-
try {
|
|
300
|
-
entries = await readdir(dir, { withFileTypes: true });
|
|
301
|
-
}
|
|
302
|
-
catch {
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
306
|
-
if (ruleFiles.length > 500)
|
|
307
|
-
return;
|
|
308
|
-
const full = path.join(dir, entry.name);
|
|
309
|
-
if (entry.isDirectory()) {
|
|
310
|
-
if (entry.name === 'node_modules' || entry.name.startsWith('.git'))
|
|
311
|
-
continue;
|
|
312
|
-
await walk(full, depth + 1);
|
|
313
|
-
}
|
|
314
|
-
else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
315
|
-
if (!full.replace(/\\/g, '/').includes('/rules/'))
|
|
316
|
-
continue;
|
|
317
|
-
ruleFiles.push(full);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
};
|
|
321
|
-
for (const dir of roots)
|
|
322
|
-
await walk(dir, 0);
|
|
323
|
-
const referenced = new Set();
|
|
324
|
-
for (const file of ruleFiles) {
|
|
325
|
-
let content;
|
|
326
|
-
try {
|
|
327
|
-
const st = await stat(file);
|
|
328
|
-
if (st.size > 65_536)
|
|
329
|
-
continue;
|
|
330
|
-
content = await readRuleFile(file, 'utf8');
|
|
331
|
-
}
|
|
332
|
-
catch {
|
|
333
|
-
continue;
|
|
334
|
-
}
|
|
335
|
-
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);
|
|
336
|
-
if (!match)
|
|
337
|
-
continue;
|
|
338
|
-
const frontmatter = match[1];
|
|
339
|
-
if (!frontmatter.includes('enforcedBy'))
|
|
340
|
-
continue;
|
|
341
|
-
const ruleName = path.basename(file, '.md');
|
|
342
|
-
const dirName = path.basename(path.dirname(file));
|
|
343
|
-
const names = new Set([ruleName, dirName === 'rules' ? null : `${dirName}/${ruleName}`, file]);
|
|
344
|
-
if (!names.has(ruleId) && ![...names].filter(Boolean).some(name => ruleId.endsWith(String(name))))
|
|
345
|
-
continue;
|
|
346
|
-
for (const line of frontmatter.split('\n')) {
|
|
347
|
-
const trimmed = line.trim().replace(/^-\s*/, '');
|
|
348
|
-
const id = trimmed.match(/(aiwg|framework|addon|extension|project):[^\s,'"\]]+/)?.[1]
|
|
349
|
-
? trimmed.match(/((?:aiwg|framework|addon|extension|project):[^\s,'"\]]+)/)?.[1]
|
|
350
|
-
: null;
|
|
351
|
-
if (id)
|
|
352
|
-
referenced.add(id.split('#')[0]);
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
return [...referenced].sort();
|
|
283
|
+
const { listRuleGateCoverage } = await import('../rules/gate-coverage.js');
|
|
284
|
+
return [...new Set(listRuleGateCoverage(options, ruleId).flatMap(rule => rule.enforcedBy.map(reference => reference.split('#')[0])))].sort();
|
|
356
285
|
}
|
|
357
286
|
/** Show one pack by full id, rest-path or short pack name. */
|
|
358
287
|
export function showGatePack(id, options = {}) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ceilingDecision, maxOutcome } from './lattice.js';
|
|
1
2
|
import { qualificationIntegrityAllowlistProblems } from '../decision/qualification/release.js';
|
|
2
3
|
import { artifactDigest } from '../decision/validate.js';
|
|
3
4
|
import { evaluatePredicate } from '../decision/predicates.js';
|
|
@@ -12,11 +13,7 @@ import { qualifyGateParameter } from './types.js';
|
|
|
12
13
|
export class GateEvaluationError extends Error {
|
|
13
14
|
constructor(message) { super(message); this.name = 'GateEvaluationError'; }
|
|
14
15
|
}
|
|
15
|
-
|
|
16
|
-
/** Shared outcome lattice. The maximum never upgrades any component. */
|
|
17
|
-
export function maxOutcome(...outcomes) {
|
|
18
|
-
return outcomes.reduce((worst, outcome) => (RANK[outcome] > RANK[worst] ? outcome : worst), 'PROMOTE');
|
|
19
|
-
}
|
|
16
|
+
export { maxOutcome } from './lattice.js';
|
|
20
17
|
/**
|
|
21
18
|
* Per-gate outcome from its own status: `pass` promotes, `fail` takes the
|
|
22
19
|
* gate's `onFail`, `insufficient` takes `onInsufficient` (always HOLD by load
|
|
@@ -447,11 +444,7 @@ function upstreamOutcome(upstream) {
|
|
|
447
444
|
const compromised = problems.includes('compromised')
|
|
448
445
|
|| release === 'ROLLBACK'
|
|
449
446
|
|| upstream.metadata.integrity_state === 'compromised';
|
|
450
|
-
|
|
451
|
-
return { ceiling: 'ROLLBACK', problems };
|
|
452
|
-
if (problems.length || release === 'HOLD')
|
|
453
|
-
return { ceiling: 'HOLD', problems };
|
|
454
|
-
return { ceiling: 'PROMOTE', problems };
|
|
447
|
+
return { ceiling: ceilingDecision(release ?? 'HOLD', compromised ? 'ROLLBACK' : 'PROMOTE', problems.length ? 'HOLD' : 'PROMOTE'), problems };
|
|
455
448
|
}
|
|
456
449
|
/**
|
|
457
450
|
* Pure, deterministic gate evaluation. The binding is resolved internally
|
|
@@ -685,7 +678,7 @@ export function evaluateGates(input) {
|
|
|
685
678
|
const packOutcome = maxOutcome(...gateEvidence.map(entry => entry.outcome));
|
|
686
679
|
const { ceiling: upstreamCeiling, problems: upstreamProblems } = upstreamOutcome(upstream);
|
|
687
680
|
const bindingCeiling = binding.spec.ceiling ?? 'PROMOTE';
|
|
688
|
-
const decision =
|
|
681
|
+
const decision = ceilingDecision(packOutcome, upstreamCeiling, bindingCeiling);
|
|
689
682
|
const body = {
|
|
690
683
|
apiVersion: binding.apiVersion,
|
|
691
684
|
kind: 'GateReport',
|
package/dist/src/gates/floors.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { GATE_RANK } from './lattice.js';
|
|
1
2
|
import { validateGateDocument } from './schema.js';
|
|
2
3
|
import { splitPackId } from './registry.js';
|
|
3
|
-
const OUTCOME_RANK = { PROMOTE: 0, HOLD: 1, ROLLBACK: 2 };
|
|
4
4
|
const OUTCOMES = ['PROMOTE', 'HOLD', 'ROLLBACK'];
|
|
5
5
|
const VERSION_PATTERN = /^\d+\.\d+\.\d+$/;
|
|
6
6
|
const DIGEST_PATTERN = /^sha256:[0-9a-f]{64}$/;
|
|
@@ -22,7 +22,7 @@ export const INTEGRITY_CEILING_FLOOR_PACK_VERSION = '1.0.0';
|
|
|
22
22
|
export function projectCeilingSatisfied(declared, configured) {
|
|
23
23
|
if (configured === undefined)
|
|
24
24
|
return true;
|
|
25
|
-
return
|
|
25
|
+
return GATE_RANK[declared ?? 'PROMOTE'] >= GATE_RANK[configured];
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
28
|
* Resolves the effective project floors: the configured entries, verbatim.
|
|
@@ -64,7 +64,7 @@ export function assertCeilingDefaults(ceilings) {
|
|
|
64
64
|
continue;
|
|
65
65
|
if (!starValid)
|
|
66
66
|
throw new Error(`gates.ceilings: per-study ceilings require the project-wide '*' default ceiling`);
|
|
67
|
-
if (
|
|
67
|
+
if (GATE_RANK[ceiling] < GATE_RANK[star]) {
|
|
68
68
|
throw new Error(`gates.ceilings.${study}: must tighten the project default ceiling ${String(star)}, not loosen it`);
|
|
69
69
|
}
|
|
70
70
|
}
|
|
@@ -124,7 +124,7 @@ export function validateGatesConfig(gates) {
|
|
|
124
124
|
for (const [study, ceiling] of Object.entries(gates.ceilings)) {
|
|
125
125
|
if (study !== '*' && typeof ceiling === 'string'
|
|
126
126
|
&& OUTCOMES.includes(ceiling)
|
|
127
|
-
&&
|
|
127
|
+
&& GATE_RANK[ceiling] < GATE_RANK[star]) {
|
|
128
128
|
errors.push(`gates.ceilings.${study}: must tighten the project default ceiling ${star}, not loosen it`);
|
|
129
129
|
}
|
|
130
130
|
}
|
package/dist/src/gates/index.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Gates capability phase 1 core (epic #2824): declarative gate packs, a pure deterministic
|
|
3
|
-
* evaluator and digest-bound reports.
|
|
4
|
-
* decision runtime imports this module, so existing behavior is unchanged.
|
|
3
|
+
* evaluator and digest-bound reports. Decision consumers share its outcome lattice.
|
|
5
4
|
*/
|
|
6
5
|
export { GATES_API_VERSION, qualifyGateParameter } from './types.js';
|
|
7
6
|
export { GateSchemaError, validateGateDocument } from './schema.js';
|
|
8
7
|
export { GateRegistryError, GATE_NAMESPACES, splitPackId, validateResolvedPack, resolveThresholdDefault, assertGateTightens, applyGateExtends, composeGatePack, resolveGateBinding, expandFloorPacks, enforceProjectFloors, authoredGatePacksOf, gateProvidersOf, GateRegistry, } from './registry.js';
|
|
9
8
|
export { INTEGRITY_CEILING_FLOOR_PACK_ID, INTEGRITY_CEILING_FLOOR_PACK_VERSION, assertCeilingDefaults, projectCeilingSatisfied, resolveProjectFloors, validateGatesConfig, } from './floors.js';
|
|
10
9
|
export { GateEvaluationError, maxOutcome, gateStatusOutcome, sealUpstream, sealGateHoldout, evaluateGates, } from './evaluate.js';
|
|
11
|
-
export { validateGateReport } from './report.js';
|
|
10
|
+
export { assertGateReportDecision, validateGateReport } from './report.js';
|
|
12
11
|
export { createCoreProviderRegistry, evidenceProvider, pairedProvider, proportionProvider, scalarProvider, screeningProvider } from './providers/index.js';
|
|
13
12
|
export { MetricProviderRegistry } from './providers/index.js';
|
|
14
13
|
export { GATE_PACKS_DIR, GATE_PACK_MAX_BYTES, isGatePackFile, listGatePackFiles, readBundleGatePacksDeclaration, loadGatePackFile, originForPackDir, registerBundleGatePacks, resolveGatePacksDir, discoverShippedGatePacks, } from './discovery.js';
|
|
15
14
|
export { buildGatesRegistry, validateGateFile, loadHoldoutFile, loadUpstreamFile, evaluateGatesFromFiles, installedAiwgRoot, listGatePacks, loadProjectFloorsForEvaluate, showGatePack, packsReferencedByRule, } from './driver.js';
|
|
16
15
|
export { GateProviderError, computeProviderCodeDigest, deriveIsolatedSeed, loadGateBundleProviders, invokeBundleProvider, providerBindingPin, runIsolatedProvider, runIsolatedProviderSync, sealProviderSection, snapshotProviderDir, isLoaderIssuedProvider, isolationLaunchPlan, isolationSandboxStatus, parseIsolatedFrame, probeIsolationSandbox, isBundleProvidersEnabled, GATE_PROVIDER_MAX_FILE_BYTES, GATE_PROVIDER_MAX_FILES, GATE_PROVIDER_MAX_TOTAL_BYTES, GATE_PROVIDER_DEFAULT_TIMEOUT_MS, GATE_PROVIDER_MAX_TIMEOUT_MS, GATE_PROVIDER_DEFAULT_MAX_RECORDS, GATE_PROVIDER_MAX_OUTPUT_BYTES, } from './providers/loader.js';
|
|
16
|
+
export { GATE_RANK, ceilingDecision, assertDecisionCeiling, GateDecisionUpgradeError } from './lattice.js';
|
|
17
17
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const GATE_RANK = Object.freeze({ PROMOTE: 0, HOLD: 1, ROLLBACK: 2 });
|
|
2
|
+
/** The strictest outcome wins; an empty set has no restriction. */
|
|
3
|
+
export function maxOutcome(...outcomes) {
|
|
4
|
+
return outcomes.reduce((worst, outcome) => GATE_RANK[outcome] > GATE_RANK[worst] ? outcome : worst, 'PROMOTE');
|
|
5
|
+
}
|
|
6
|
+
export function ceilingDecision(decision, ...ceilings) {
|
|
7
|
+
return maxOutcome(decision, ...ceilings);
|
|
8
|
+
}
|
|
9
|
+
export class GateDecisionUpgradeError extends Error {
|
|
10
|
+
code = 'report-upgrades-component';
|
|
11
|
+
decision;
|
|
12
|
+
component;
|
|
13
|
+
constructor(decision, component) {
|
|
14
|
+
super(`Report decision differs from components: cannot upgrade ${component} to ${decision} (report-upgrades-component)`);
|
|
15
|
+
this.name = 'GateDecisionUpgradeError';
|
|
16
|
+
this.decision = decision;
|
|
17
|
+
this.component = component;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/** Validate a carried verdict independently of its digest or reproducibility checks. */
|
|
21
|
+
export function assertDecisionCeiling(decision, ...components) {
|
|
22
|
+
const component = maxOutcome(...components);
|
|
23
|
+
if (GATE_RANK[decision] < GATE_RANK[component])
|
|
24
|
+
throw new GateDecisionUpgradeError(decision, component);
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=lattice.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ceilingDecision } from './lattice.js';
|
|
1
2
|
import { canonicalJson } from '../security/artifact-trust.js';
|
|
2
3
|
import { artifactDigest } from '../decision/validate.js';
|
|
3
4
|
import { validateGateDocument } from './schema.js';
|
|
@@ -428,7 +429,7 @@ export function assertGateTightens(parent, child, params) {
|
|
|
428
429
|
else if (minimumAfter < minimumBefore) {
|
|
429
430
|
fail(`gate ${child.id} may only raise minimumN`);
|
|
430
431
|
}
|
|
431
|
-
if (parent.onFail
|
|
432
|
+
if (ceilingDecision(child.onFail, parent.onFail) !== child.onFail) {
|
|
432
433
|
fail(`gate ${child.id} may not demote onFail ROLLBACK -> HOLD`);
|
|
433
434
|
}
|
|
434
435
|
// Insufficient evidence always holds: ROLLBACK needs an observed blocking
|
package/dist/src/gates/report.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { assertDecisionCeiling, GateDecisionUpgradeError } from './lattice.js';
|
|
1
2
|
import { canonicalJson } from '../security/artifact-trust.js';
|
|
2
3
|
import { artifactDigest } from '../decision/validate.js';
|
|
3
|
-
import { evaluateGates
|
|
4
|
+
import { evaluateGates } from './evaluate.js';
|
|
4
5
|
import { validateGateDocument } from './schema.js';
|
|
5
6
|
/**
|
|
6
7
|
* Validates a gate report against its trusted inputs. The report is re-derived
|
|
@@ -85,12 +86,14 @@ export function validateGateReport(report, input) {
|
|
|
85
86
|
}
|
|
86
87
|
if (expected.decision !== parsed.decision)
|
|
87
88
|
reasons.push('report-decision-mismatch');
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
parsed.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
89
|
+
try {
|
|
90
|
+
assertGateReportDecision(parsed);
|
|
91
|
+
assertDecisionCeiling(parsed.decision, input.upstream?.metadata.release_gate.decision ?? 'PROMOTE', input.binding.spec.ceiling ?? 'PROMOTE');
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
if (!(error instanceof GateDecisionUpgradeError))
|
|
95
|
+
throw error;
|
|
96
|
+
return { valid: false, reasons: [...reasons, error.code], error };
|
|
94
97
|
}
|
|
95
98
|
return { valid: reasons.length === 0, reasons };
|
|
96
99
|
}
|
|
@@ -99,4 +102,13 @@ function stripDigest(report) {
|
|
|
99
102
|
void _dropped;
|
|
100
103
|
return fields;
|
|
101
104
|
}
|
|
105
|
+
/** Validate carried component ceilings even when no trusted replay context is available. */
|
|
106
|
+
export function assertGateReportDecision(report) {
|
|
107
|
+
const components = [
|
|
108
|
+
...report.gateEvidence.map(entry => entry.outcome),
|
|
109
|
+
report.ceilings.packOutcome, report.ceilings.upstreamCeiling, report.ceilings.bindingCeiling,
|
|
110
|
+
report.upstream?.decision ?? 'PROMOTE',
|
|
111
|
+
];
|
|
112
|
+
assertDecisionCeiling(report.decision, ...components);
|
|
113
|
+
}
|
|
102
114
|
//# sourceMappingURL=report.js.map
|