@bitkyc08/opencodex 2.11.1 → 2.12.0
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/README.md +1 -1
- package/bin/ocx.mjs +4 -0
- package/gui/dist/assets/index-BNVYzdn0.css +1 -0
- package/gui/dist/assets/index-Cw1_mxO8.js +70 -0
- package/gui/dist/index.html +2 -2
- package/package.json +1 -1
- package/src/adapters/command-code.ts +94 -5
- package/src/adapters/cursor/live-transport.ts +14 -1
- package/src/adapters/google-tool-schema.ts +80 -15
- package/src/adapters/google.ts +24 -7
- package/src/adapters/openai-chat.ts +233 -305
- package/src/adapters/openai-responses.ts +62 -0
- package/src/adapters/tool-catalog-nudge.ts +2 -11
- package/src/claude/outbound.ts +42 -14
- package/src/cli/claude.ts +61 -9
- package/src/cli/codex-shim-autorestore.ts +2 -0
- package/src/cli/codex-shim-readiness.ts +8 -1
- package/src/cli/config-command.ts +4 -1
- package/src/cli/doctor.ts +10 -0
- package/src/cli/help.ts +15 -0
- package/src/cli/index.ts +27 -11
- package/src/cli/lab.ts +367 -0
- package/src/cli/provider.ts +9 -1
- package/src/clients/config-export.ts +100 -3
- package/src/codex/app-server-processes.ts +11 -8
- package/src/codex/auth-api.ts +92 -28
- package/src/codex/auth-collision.ts +5 -3
- package/src/codex/catalog/metadata.ts +7 -3
- package/src/codex/catalog/provider-fetch.ts +191 -47
- package/src/codex/catalog.ts +2 -2
- package/src/codex/history-job.ts +83 -18
- package/src/codex/history-migration-guardian.ts +9 -16
- package/src/codex/history-provider.ts +178 -1
- package/src/codex/history-transition.ts +5 -5
- package/src/codex/history-worker.ts +22 -2
- package/src/codex/native-profile-store.ts +1 -1
- package/src/codex/plan.ts +15 -0
- package/src/codex/quota.ts +6 -6
- package/src/codex/routing.ts +3 -3
- package/src/codex/subagent-model-fallback.ts +246 -22
- package/src/codex/user-identity.ts +63 -12
- package/src/config.ts +265 -32
- package/src/images/loop.ts +3 -1
- package/src/images/plan.ts +23 -13
- package/src/integrations/omp-yaml-source.ts +225 -0
- package/src/integrations/ownership.ts +5 -5
- package/src/integrations/state.ts +45 -6
- package/src/integrations/writer.ts +38 -3
- package/src/lab/artifacts/sanitize.ts +586 -0
- package/src/lab/artifacts/secure-fs.ts +475 -0
- package/src/lab/artifacts/store.ts +310 -0
- package/src/lab/conformance/assertion.ts +325 -0
- package/src/lab/conformance/digest.ts +22 -0
- package/src/lab/conformance/executor.ts +741 -0
- package/src/lab/conformance/fixture-provider.ts +27 -0
- package/src/lab/conformance/fixtures/live-v1-cases.json +175 -0
- package/src/lab/conformance/fixtures/protocol-v1-cases.json +461 -0
- package/src/lab/conformance/harness-budget.ts +47 -0
- package/src/lab/conformance/index.ts +5 -0
- package/src/lab/conformance/jcs.ts +24 -0
- package/src/lab/conformance/json-pointer.ts +39 -0
- package/src/lab/conformance/manifest.ts +180 -0
- package/src/lab/conformance/mcp-stub.ts +179 -0
- package/src/lab/conformance/negative-controls.ts +160 -0
- package/src/lab/conformance/observation.ts +355 -0
- package/src/lab/conformance/runner.ts +57 -0
- package/src/lab/conformance/sse-normalize.ts +59 -0
- package/src/lab/conformance/suite-manifest.ts +78 -0
- package/src/lab/conformance/types.ts +191 -0
- package/src/lab/constants.ts +126 -0
- package/src/lab/digest.ts +64 -0
- package/src/lab/events/errors.ts +9 -0
- package/src/lab/events/limits.ts +117 -0
- package/src/lab/events/types.ts +228 -0
- package/src/lab/events/validate.ts +780 -0
- package/src/lab/index.ts +36 -0
- package/src/lab/ledger/artifact-refs.ts +127 -0
- package/src/lab/ledger/invalidation.ts +136 -0
- package/src/lab/ledger/purge.ts +241 -0
- package/src/lab/ledger/store.ts +263 -0
- package/src/lab/live/credential-lease.ts +53 -0
- package/src/lab/live/destination.ts +155 -0
- package/src/lab/live/executor.ts +311 -0
- package/src/lab/live/inert-tools.ts +56 -0
- package/src/lab/live/manifest.ts +85 -0
- package/src/lab/live/mcp-loopback.ts +57 -0
- package/src/lab/live/runner.ts +19 -0
- package/src/lab/live/sandbox.ts +61 -0
- package/src/lab/live/suite-manifest.ts +41 -0
- package/src/lab/live/transport.ts +116 -0
- package/src/lab/live/types.ts +197 -0
- package/src/lab/observe/from-conformance.ts +344 -0
- package/src/lab/observe/from-live.ts +103 -0
- package/src/lab/paths.ts +65 -0
- package/src/lab/projection/rebuild.ts +477 -0
- package/src/lab/projection/schema.ts +122 -0
- package/src/lab/projection/verdicts.ts +438 -0
- package/src/lab/projection/verification.ts +332 -0
- package/src/lab/query/catalog.ts +72 -0
- package/src/lab/query/connection.ts +107 -0
- package/src/lab/query/constants.ts +4 -0
- package/src/lab/query/cursor.ts +132 -0
- package/src/lab/query/dto-map.ts +277 -0
- package/src/lab/query/errors.ts +22 -0
- package/src/lab/query/index.ts +34 -0
- package/src/lab/query/queries.ts +444 -0
- package/src/lab/query/types.ts +266 -0
- package/src/lab/subject/behavior-fingerprint.ts +77 -0
- package/src/lab/subject/installation-salt.ts +81 -0
- package/src/lab/subject/route-subject.ts +73 -0
- package/src/lib/bounded-body.ts +104 -0
- package/src/lib/lab-live-execution-authority.ts +13 -0
- package/src/lib/lab-live-host.ts +30 -0
- package/src/lib/lab-live-pinned-sender.ts +45 -0
- package/src/lib/pinned-http.ts +36 -7
- package/src/oauth/index.ts +20 -1
- package/src/oauth/local-token-detect.ts +11 -2
- package/src/oauth/login-cli.ts +38 -4
- package/src/providers/alibaba-region-migration.ts +1 -1
- package/src/providers/codex-capacity.ts +7 -4
- package/src/providers/command-code-efforts.ts +5 -2
- package/src/providers/derive.ts +105 -0
- package/src/providers/label.ts +1 -1
- package/src/providers/openai-tiers.ts +59 -3
- package/src/providers/opencode-zen-rate-limit.ts +102 -0
- package/src/providers/quota.ts +67 -21
- package/src/providers/registry.ts +118 -5
- package/src/router.ts +15 -1
- package/src/routing/analytics.ts +8 -7
- package/src/routing/evaluator.ts +51 -5
- package/src/routing/profile.ts +10 -1
- package/src/routing/trace.ts +42 -6
- package/src/server/auth-cors.ts +15 -0
- package/src/server/background-lifecycle.ts +169 -0
- package/src/server/chat-completions.ts +18 -3
- package/src/server/claude-messages.ts +1 -0
- package/src/server/images.ts +78 -11
- package/src/server/index.ts +72 -18
- package/src/server/lifecycle.ts +6 -0
- package/src/server/live.ts +5 -1
- package/src/server/management/agent-settings-routes.ts +41 -24
- package/src/server/management/body.ts +6 -0
- package/src/server/management/config-routes.ts +51 -8
- package/src/server/management/lab-routes.ts +388 -0
- package/src/server/management/logs-usage-routes.ts +19 -1
- package/src/server/management/model-rows.ts +25 -11
- package/src/server/management/native-integration-routes.ts +3 -3
- package/src/server/management/provider-routes.ts +5 -0
- package/src/server/management/shared.ts +8 -1
- package/src/server/management/usage-summary-cache.ts +2 -0
- package/src/server/management/vision-sidecar-options.ts +125 -0
- package/src/server/management-api.ts +2 -0
- package/src/server/request-decompress.ts +106 -5
- package/src/server/responses/collaboration.ts +4 -1
- package/src/server/responses/core.ts +117 -31
- package/src/server/responses-json-events.ts +50 -12
- package/src/server/search.ts +23 -7
- package/src/server/ws-bridge.ts +3 -1
- package/src/service.ts +40 -20
- package/src/types.ts +74 -0
- package/src/update/job.ts +10 -2
- package/src/usage/cost.ts +0 -0
- package/src/usage/expected-prices.ts +9 -0
- package/src/usage/log.ts +9 -9
- package/src/usage/user-cost-overlay-reconciler.ts +313 -0
- package/src/usage/user-cost-overlays.ts +314 -0
- package/src/vision/describe.ts +2 -2
- package/src/vision/eligibility.ts +229 -0
- package/src/vision/index.ts +20 -2
- package/src/web-search/executor.ts +2 -2
- package/src/web-search/index.ts +4 -2
- package/src/web-search/loop.ts +3 -1
- package/src/web-search/parse.ts +72 -3
- package/gui/dist/assets/index-BA1dgo4q.css +0 -1
- package/gui/dist/assets/index-DDZpgzKk.js +0 -70
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disposable SQLite projection schema for Compatibility Lab (CL-02).
|
|
3
|
+
* Not canonical storage — rebuildable from JSONL + content-addressed artifacts.
|
|
4
|
+
*/
|
|
5
|
+
export const LAB_SQLITE_SCHEMA_VERSION = 2;
|
|
6
|
+
|
|
7
|
+
export const LAB_SQLITE_DDL = `
|
|
8
|
+
CREATE TABLE IF NOT EXISTS schema_meta (
|
|
9
|
+
key TEXT PRIMARY KEY,
|
|
10
|
+
value TEXT NOT NULL
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
CREATE TABLE IF NOT EXISTS events (
|
|
14
|
+
event_id TEXT PRIMARY KEY,
|
|
15
|
+
event_kind TEXT NOT NULL CHECK (event_kind IN ('observation', 'claim_snapshot', 'invalidation', 'purge_tombstone')),
|
|
16
|
+
recorded_at INTEGER NOT NULL,
|
|
17
|
+
producer TEXT NOT NULL,
|
|
18
|
+
producer_version TEXT NOT NULL,
|
|
19
|
+
payload_json TEXT NOT NULL,
|
|
20
|
+
excluded INTEGER NOT NULL DEFAULT 0 CHECK (excluded IN (0, 1)),
|
|
21
|
+
exclusion_reason TEXT
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
CREATE INDEX IF NOT EXISTS idx_events_kind ON events(event_kind, recorded_at);
|
|
25
|
+
CREATE INDEX IF NOT EXISTS idx_events_excluded ON events(excluded);
|
|
26
|
+
|
|
27
|
+
CREATE TABLE IF NOT EXISTS subjects (
|
|
28
|
+
subject_id TEXT PRIMARY KEY,
|
|
29
|
+
subject_kind TEXT NOT NULL,
|
|
30
|
+
subject_json TEXT NOT NULL
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
CREATE TABLE IF NOT EXISTS observations (
|
|
34
|
+
event_id TEXT PRIMARY KEY,
|
|
35
|
+
subject_id TEXT NOT NULL,
|
|
36
|
+
evidence_layer TEXT NOT NULL CHECK (evidence_layer IN ('protocol_conformance', 'live_route_compatibility', 'task_effectiveness')),
|
|
37
|
+
suite_id TEXT NOT NULL,
|
|
38
|
+
suite_version TEXT NOT NULL,
|
|
39
|
+
suite_manifest_digest TEXT NOT NULL,
|
|
40
|
+
scenario_id TEXT NOT NULL,
|
|
41
|
+
scenario_version TEXT NOT NULL,
|
|
42
|
+
scenario_manifest_digest TEXT NOT NULL,
|
|
43
|
+
outcome TEXT NOT NULL CHECK (outcome IN ('pass', 'fail', 'blocked', 'inconclusive')),
|
|
44
|
+
completed_at INTEGER NOT NULL,
|
|
45
|
+
execution_mode TEXT NOT NULL CHECK (execution_mode IN ('fixture', 'live', 'fabric')),
|
|
46
|
+
FOREIGN KEY(event_id) REFERENCES events(event_id)
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
CREATE INDEX IF NOT EXISTS idx_observations_proj ON observations(
|
|
50
|
+
subject_id, evidence_layer, suite_id, suite_version, suite_manifest_digest, completed_at
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
CREATE TABLE IF NOT EXISTS claims (
|
|
54
|
+
event_id TEXT PRIMARY KEY,
|
|
55
|
+
subject_id TEXT NOT NULL,
|
|
56
|
+
capability TEXT NOT NULL,
|
|
57
|
+
polarity TEXT NOT NULL CHECK (polarity IN ('supported', 'not_supported', 'withdrawn')),
|
|
58
|
+
source_manifest_digest TEXT NOT NULL,
|
|
59
|
+
effective_at INTEGER NOT NULL,
|
|
60
|
+
recorded_at INTEGER NOT NULL,
|
|
61
|
+
supersedes_json TEXT NOT NULL,
|
|
62
|
+
current INTEGER NOT NULL DEFAULT 0 CHECK (current IN (0, 1)),
|
|
63
|
+
usable INTEGER NOT NULL DEFAULT 1 CHECK (usable IN (0, 1)),
|
|
64
|
+
FOREIGN KEY(event_id) REFERENCES events(event_id)
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
CREATE INDEX IF NOT EXISTS idx_claims_key ON claims(subject_id, capability, effective_at, event_id);
|
|
68
|
+
|
|
69
|
+
CREATE TABLE IF NOT EXISTS invalidations (
|
|
70
|
+
event_id TEXT PRIMARY KEY,
|
|
71
|
+
reason TEXT NOT NULL,
|
|
72
|
+
targets_json TEXT NOT NULL,
|
|
73
|
+
recorded_at INTEGER NOT NULL,
|
|
74
|
+
applied INTEGER NOT NULL DEFAULT 0 CHECK (applied IN (0, 1)),
|
|
75
|
+
FOREIGN KEY(event_id) REFERENCES events(event_id)
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
CREATE TABLE IF NOT EXISTS purges (
|
|
79
|
+
event_id TEXT PRIMARY KEY,
|
|
80
|
+
target_event_ids_json TEXT NOT NULL,
|
|
81
|
+
target_artifact_digests_json TEXT NOT NULL,
|
|
82
|
+
purge_actions_json TEXT NOT NULL,
|
|
83
|
+
recorded_at INTEGER NOT NULL,
|
|
84
|
+
FOREIGN KEY(event_id) REFERENCES events(event_id)
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
CREATE TABLE IF NOT EXISTS artifacts (
|
|
88
|
+
digest TEXT PRIMARY KEY,
|
|
89
|
+
artifact_class TEXT,
|
|
90
|
+
media_type TEXT,
|
|
91
|
+
byte_count INTEGER,
|
|
92
|
+
status TEXT NOT NULL CHECK (status IN ('present', 'corrupt', 'purged_unavailable')),
|
|
93
|
+
last_error TEXT
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
CREATE TABLE IF NOT EXISTS verdicts (
|
|
97
|
+
projection_key TEXT PRIMARY KEY,
|
|
98
|
+
subject_id TEXT NOT NULL,
|
|
99
|
+
evidence_layer TEXT NOT NULL,
|
|
100
|
+
suite_id TEXT NOT NULL,
|
|
101
|
+
suite_version TEXT NOT NULL,
|
|
102
|
+
suite_manifest_digest TEXT NOT NULL,
|
|
103
|
+
projection_spec_version TEXT NOT NULL,
|
|
104
|
+
verdict TEXT NOT NULL CHECK (verdict IN ('UNKNOWN', 'CLAIMED', 'PROBED', 'VERIFIED', 'DEGRADED', 'BLOCKED', 'UNSUPPORTED')),
|
|
105
|
+
as_of INTEGER NOT NULL,
|
|
106
|
+
scenario_manifest_digests_json TEXT NOT NULL,
|
|
107
|
+
claim_source_digest TEXT,
|
|
108
|
+
contributing_event_ids_json TEXT NOT NULL,
|
|
109
|
+
contradicting_event_ids_json TEXT NOT NULL,
|
|
110
|
+
notes_json TEXT NOT NULL
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
CREATE INDEX IF NOT EXISTS idx_verdicts_subject ON verdicts(subject_id, evidence_layer, suite_id);
|
|
114
|
+
|
|
115
|
+
CREATE TABLE IF NOT EXISTS corruption (
|
|
116
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
117
|
+
kind TEXT NOT NULL,
|
|
118
|
+
line_number INTEGER,
|
|
119
|
+
event_id TEXT,
|
|
120
|
+
detail TEXT NOT NULL
|
|
121
|
+
);
|
|
122
|
+
`;
|
|
@@ -0,0 +1,438 @@
|
|
|
1
|
+
import type { CompatibilityVerdict } from "../constants";
|
|
2
|
+
import { LAB_PROJECTION_SPEC_VERSION } from "../constants";
|
|
3
|
+
import type { SuiteManifestV1 } from "../conformance/suite-manifest";
|
|
4
|
+
import { jcsStringify } from "../digest";
|
|
5
|
+
import type {
|
|
6
|
+
ClaimSnapshotEvent,
|
|
7
|
+
LabEvent,
|
|
8
|
+
LedgerCorruption,
|
|
9
|
+
ObservationEvent,
|
|
10
|
+
} from "../events/types";
|
|
11
|
+
import {
|
|
12
|
+
buildInvalidationIndex,
|
|
13
|
+
isEventExcluded,
|
|
14
|
+
usableClaims,
|
|
15
|
+
usableObservations,
|
|
16
|
+
type InvalidationIndex,
|
|
17
|
+
} from "../ledger/invalidation";
|
|
18
|
+
import {
|
|
19
|
+
evaluateAllApplicableRequiredPassV1,
|
|
20
|
+
newestObservationByScenario,
|
|
21
|
+
type ScenarioRequirements,
|
|
22
|
+
} from "./verification";
|
|
23
|
+
|
|
24
|
+
export interface ProjectionKey {
|
|
25
|
+
subjectId: string;
|
|
26
|
+
evidenceLayer: string;
|
|
27
|
+
suiteId: string;
|
|
28
|
+
suiteVersion: string;
|
|
29
|
+
suiteManifestDigest: string;
|
|
30
|
+
projectionSpecVersion: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function componentKey(parts: readonly string[]): string {
|
|
34
|
+
return jcsStringify([...parts]);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function projectionKeyString(key: ProjectionKey): string {
|
|
38
|
+
return componentKey([
|
|
39
|
+
key.subjectId,
|
|
40
|
+
key.evidenceLayer,
|
|
41
|
+
key.suiteId,
|
|
42
|
+
key.suiteVersion,
|
|
43
|
+
key.suiteManifestDigest,
|
|
44
|
+
key.projectionSpecVersion,
|
|
45
|
+
]);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function claimKeyString(subjectId: string, capability: string): string {
|
|
49
|
+
return componentKey([subjectId, capability]);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface DerivedVerdict {
|
|
53
|
+
key: ProjectionKey;
|
|
54
|
+
verdict: CompatibilityVerdict;
|
|
55
|
+
asOf: number;
|
|
56
|
+
scenarioManifestDigests: string[];
|
|
57
|
+
claimSourceDigest?: string;
|
|
58
|
+
contributingEventIds: string[];
|
|
59
|
+
contradictingEventIds: string[];
|
|
60
|
+
notes: string[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export interface ClaimState {
|
|
64
|
+
key: string;
|
|
65
|
+
current: ClaimSnapshotEvent | null;
|
|
66
|
+
corruption?: string;
|
|
67
|
+
unusable?: boolean;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface ProjectVerdictsOptions {
|
|
71
|
+
asOf?: number;
|
|
72
|
+
index?: InvalidationIndex;
|
|
73
|
+
unusableObservationIds?: Set<string>;
|
|
74
|
+
unusableClaimEventIds?: Set<string>;
|
|
75
|
+
loadSuiteManifest?: (digest: string) => SuiteManifestV1 | null;
|
|
76
|
+
loadScenarioManifest?: (digest: string) => Record<string, unknown> | null;
|
|
77
|
+
loadScenarioRequirements?: (digest: string) => ScenarioRequirements | null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Resolve current claims after purge/invalidation and supersession.
|
|
82
|
+
* Multiple unsuperseded claims, missing predecessors, cross-key supersession, or cycles → UNKNOWN + corruption.
|
|
83
|
+
*/
|
|
84
|
+
export function resolveClaimStates(
|
|
85
|
+
claims: ClaimSnapshotEvent[],
|
|
86
|
+
opts: {
|
|
87
|
+
unusableClaimEventIds?: Set<string>;
|
|
88
|
+
purgedEventIds?: ReadonlySet<string>;
|
|
89
|
+
} = {},
|
|
90
|
+
): {
|
|
91
|
+
states: Map<string, ClaimState>;
|
|
92
|
+
corruptions: LedgerCorruption[];
|
|
93
|
+
} {
|
|
94
|
+
const unusableClaims = opts.unusableClaimEventIds ?? new Set<string>();
|
|
95
|
+
const purgedEventIds = opts.purgedEventIds ?? new Set<string>();
|
|
96
|
+
const byKey = new Map<string, ClaimSnapshotEvent[]>();
|
|
97
|
+
const allById = new Map(claims.map((claim) => [claim.eventId, claim]));
|
|
98
|
+
for (const claim of claims) {
|
|
99
|
+
const key = claimKeyString(claim.subjectId, claim.capability);
|
|
100
|
+
const list = byKey.get(key) ?? [];
|
|
101
|
+
list.push(claim);
|
|
102
|
+
byKey.set(key, list);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const states = new Map<string, ClaimState>();
|
|
106
|
+
const corruptions: LedgerCorruption[] = [];
|
|
107
|
+
|
|
108
|
+
for (const [key, list] of byKey) {
|
|
109
|
+
const sorted = [...list].sort((a, b) => {
|
|
110
|
+
if (a.effectiveAt !== b.effectiveAt) return a.effectiveAt - b.effectiveAt;
|
|
111
|
+
if (a.recordedAt !== b.recordedAt) return a.recordedAt - b.recordedAt;
|
|
112
|
+
return a.eventId < b.eventId ? -1 : a.eventId > b.eventId ? 1 : 0;
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const superseded = new Set<string>();
|
|
116
|
+
|
|
117
|
+
for (const claim of sorted) {
|
|
118
|
+
for (const pred of claim.supersedes) {
|
|
119
|
+
const prev = allById.get(pred);
|
|
120
|
+
if (!prev) {
|
|
121
|
+
if (purgedEventIds.has(pred)) {
|
|
122
|
+
// A purge may physically remove a superseded predecessor. The ID remains
|
|
123
|
+
// valid provenance but is not a live claim candidate.
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
corruptions.push({
|
|
127
|
+
kind: "claim_corruption",
|
|
128
|
+
eventId: claim.eventId,
|
|
129
|
+
detail: `missing supersedes predecessor ${pred}`,
|
|
130
|
+
});
|
|
131
|
+
states.set(key, { key, current: null, corruption: "missing predecessor" });
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (prev.subjectId !== claim.subjectId || prev.capability !== claim.capability) {
|
|
135
|
+
corruptions.push({
|
|
136
|
+
kind: "claim_corruption",
|
|
137
|
+
eventId: claim.eventId,
|
|
138
|
+
detail: "cross-key supersession",
|
|
139
|
+
});
|
|
140
|
+
states.set(key, { key, current: null, corruption: "cross-key supersession" });
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
superseded.add(pred);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (states.get(key)?.corruption) continue;
|
|
148
|
+
|
|
149
|
+
const current = sorted.filter((c) => !superseded.has(c.eventId));
|
|
150
|
+
if (current.length > 1) {
|
|
151
|
+
corruptions.push({
|
|
152
|
+
kind: "claim_corruption",
|
|
153
|
+
detail: `multiple unsuperseded claims for ${key}`,
|
|
154
|
+
});
|
|
155
|
+
states.set(key, { key, current: null, corruption: "conflicting current claims" });
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
const currentClaim = current[0] ?? null;
|
|
159
|
+
states.set(key, {
|
|
160
|
+
key,
|
|
161
|
+
current: currentClaim,
|
|
162
|
+
unusable: currentClaim ? unusableClaims.has(currentClaim.eventId) : undefined,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return { states, corruptions };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function supportedClaimsForSubject(claimStates: Map<string, ClaimState>, subjectId: string): string[] {
|
|
170
|
+
const supported = new Set<string>();
|
|
171
|
+
for (const state of claimStates.values()) {
|
|
172
|
+
const claim = state.current;
|
|
173
|
+
if (!claim || state.corruption || state.unusable) continue;
|
|
174
|
+
if (claim.subjectId === subjectId && claim.polarity === "supported") supported.add(claim.capability);
|
|
175
|
+
}
|
|
176
|
+
return [...supported].sort();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* CL-02 verdict projection primitives with frozen CL-00 verification semantics.
|
|
181
|
+
*/
|
|
182
|
+
export function projectVerdicts(
|
|
183
|
+
events: LabEvent[],
|
|
184
|
+
opts: ProjectVerdictsOptions = {},
|
|
185
|
+
): { verdicts: DerivedVerdict[]; corruptions: LedgerCorruption[]; index: InvalidationIndex } {
|
|
186
|
+
const index = opts.index ?? buildInvalidationIndex(events);
|
|
187
|
+
const corruptions = [...index.corruptions];
|
|
188
|
+
const unusableObs = opts.unusableObservationIds ?? new Set<string>();
|
|
189
|
+
const unusableClaims = opts.unusableClaimEventIds ?? new Set<string>();
|
|
190
|
+
const asOf =
|
|
191
|
+
opts.asOf ??
|
|
192
|
+
events.reduce((max, event) => {
|
|
193
|
+
if (event.eventKind === "observation") return Math.max(max, event.completedAt, event.recordedAt);
|
|
194
|
+
if (event.eventKind === "claim_snapshot") return Math.max(max, event.effectiveAt, event.recordedAt);
|
|
195
|
+
return Math.max(max, event.recordedAt);
|
|
196
|
+
}, 0);
|
|
197
|
+
|
|
198
|
+
const observations = usableObservations(events, index)
|
|
199
|
+
.filter((o) => o.completedAt <= asOf)
|
|
200
|
+
.filter((o) => !unusableObs.has(o.eventId));
|
|
201
|
+
const claims = usableClaims(events, index).filter((c) => c.effectiveAt <= asOf);
|
|
202
|
+
const { states: claimStates, corruptions: claimCorruptions } = resolveClaimStates(claims, {
|
|
203
|
+
unusableClaimEventIds: unusableClaims,
|
|
204
|
+
purgedEventIds: index.purgedEventIds,
|
|
205
|
+
});
|
|
206
|
+
corruptions.push(...claimCorruptions);
|
|
207
|
+
|
|
208
|
+
const groups = new Map<string, ObservationEvent[]>();
|
|
209
|
+
for (const obs of observations) {
|
|
210
|
+
const key: ProjectionKey = {
|
|
211
|
+
subjectId: obs.subjectId,
|
|
212
|
+
evidenceLayer: obs.evidenceLayer,
|
|
213
|
+
suiteId: obs.suiteId,
|
|
214
|
+
suiteVersion: obs.suiteVersion,
|
|
215
|
+
suiteManifestDigest: obs.suiteManifestDigest,
|
|
216
|
+
projectionSpecVersion: LAB_PROJECTION_SPEC_VERSION,
|
|
217
|
+
};
|
|
218
|
+
const ks = projectionKeyString(key);
|
|
219
|
+
const list = groups.get(ks) ?? [];
|
|
220
|
+
list.push(obs);
|
|
221
|
+
groups.set(ks, list);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const verdicts: DerivedVerdict[] = [];
|
|
225
|
+
|
|
226
|
+
for (const [, list] of groups) {
|
|
227
|
+
const sample = list[0]!;
|
|
228
|
+
const key: ProjectionKey = {
|
|
229
|
+
subjectId: sample.subjectId,
|
|
230
|
+
evidenceLayer: sample.evidenceLayer,
|
|
231
|
+
suiteId: sample.suiteId,
|
|
232
|
+
suiteVersion: sample.suiteVersion,
|
|
233
|
+
suiteManifestDigest: sample.suiteManifestDigest,
|
|
234
|
+
projectionSpecVersion: LAB_PROJECTION_SPEC_VERSION,
|
|
235
|
+
};
|
|
236
|
+
const ordered = [...list].sort((a, b) => {
|
|
237
|
+
if (a.completedAt !== b.completedAt) return a.completedAt - b.completedAt;
|
|
238
|
+
return a.eventId < b.eventId ? -1 : a.eventId > b.eventId ? 1 : 0;
|
|
239
|
+
});
|
|
240
|
+
const suiteManifest = opts.loadSuiteManifest?.(key.suiteManifestDigest) ?? null;
|
|
241
|
+
verdicts.push(
|
|
242
|
+
projectObservationGroup(key, ordered, asOf, suiteManifest, {
|
|
243
|
+
loadScenarioManifest: opts.loadScenarioManifest,
|
|
244
|
+
loadScenarioRequirements: opts.loadScenarioRequirements,
|
|
245
|
+
routeSupportedClaims: key.evidenceLayer === "live_route_compatibility"
|
|
246
|
+
? supportedClaimsForSubject(claimStates, key.subjectId)
|
|
247
|
+
: undefined,
|
|
248
|
+
}),
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
for (const [, state] of claimStates) {
|
|
253
|
+
if (!state.current || state.current.polarity !== "supported") continue;
|
|
254
|
+
if (state.corruption) continue;
|
|
255
|
+
const claim = state.current;
|
|
256
|
+
const key: ProjectionKey = {
|
|
257
|
+
subjectId: claim.subjectId,
|
|
258
|
+
evidenceLayer: "live_route_compatibility",
|
|
259
|
+
suiteId: "_claims",
|
|
260
|
+
suiteVersion: "1",
|
|
261
|
+
suiteManifestDigest: claim.sourceManifestDigest,
|
|
262
|
+
projectionSpecVersion: LAB_PROJECTION_SPEC_VERSION,
|
|
263
|
+
};
|
|
264
|
+
const ks = projectionKeyString(key);
|
|
265
|
+
if (verdicts.some((v) => projectionKeyString(v.key) === ks)) continue;
|
|
266
|
+
if (state.unusable) {
|
|
267
|
+
verdicts.push({
|
|
268
|
+
key,
|
|
269
|
+
verdict: "UNKNOWN",
|
|
270
|
+
asOf,
|
|
271
|
+
scenarioManifestDigests: [],
|
|
272
|
+
claimSourceDigest: claim.sourceManifestDigest,
|
|
273
|
+
contributingEventIds: [claim.eventId],
|
|
274
|
+
contradictingEventIds: [],
|
|
275
|
+
notes: ["current_claim_unusable"],
|
|
276
|
+
});
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
verdicts.push({
|
|
280
|
+
key,
|
|
281
|
+
verdict: "CLAIMED",
|
|
282
|
+
asOf,
|
|
283
|
+
scenarioManifestDigests: [],
|
|
284
|
+
claimSourceDigest: claim.sourceManifestDigest,
|
|
285
|
+
contributingEventIds: [claim.eventId],
|
|
286
|
+
contradictingEventIds: [],
|
|
287
|
+
notes: ["claim_snapshot"],
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return { verdicts, corruptions, index };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function isMatchedCapabilityAbsenceControl(obs: ObservationEvent): boolean {
|
|
295
|
+
const expected = obs.expectedFailure;
|
|
296
|
+
return (
|
|
297
|
+
obs.outcome === "pass" &&
|
|
298
|
+
!!expected &&
|
|
299
|
+
expected.controlKind === "capability_absence_control" &&
|
|
300
|
+
expected.onMatch === "unsupported"
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function projectObservationGroup(
|
|
305
|
+
key: ProjectionKey,
|
|
306
|
+
ordered: ObservationEvent[],
|
|
307
|
+
asOf: number,
|
|
308
|
+
suiteManifest: SuiteManifestV1 | null,
|
|
309
|
+
opts: {
|
|
310
|
+
loadScenarioManifest?: (digest: string) => Record<string, unknown> | null;
|
|
311
|
+
loadScenarioRequirements?: ProjectVerdictsOptions["loadScenarioRequirements"];
|
|
312
|
+
routeSupportedClaims?: readonly string[];
|
|
313
|
+
} = {},
|
|
314
|
+
): DerivedVerdict {
|
|
315
|
+
const contributing: string[] = [];
|
|
316
|
+
const contradicting: string[] = [];
|
|
317
|
+
const digests = new Set<string>();
|
|
318
|
+
const notes: string[] = [];
|
|
319
|
+
|
|
320
|
+
for (const obs of ordered) {
|
|
321
|
+
digests.add(obs.scenarioManifestDigest);
|
|
322
|
+
contributing.push(obs.eventId);
|
|
323
|
+
if (obs.outcome === "fail") contradicting.push(obs.eventId);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const newest = newestObservationByScenario(ordered);
|
|
327
|
+
const currentObservations = [...newest.values()];
|
|
328
|
+
const currentFails = currentObservations.filter((o) => o.outcome === "fail");
|
|
329
|
+
const currentPasses = currentObservations.filter((o) => o.outcome === "pass");
|
|
330
|
+
const currentBlocked = currentObservations.some((o) => o.outcome === "blocked");
|
|
331
|
+
const currentInconclusive = currentObservations.some((o) => o.outcome === "inconclusive");
|
|
332
|
+
const matchedCapabilityAbsence = currentObservations.some(isMatchedCapabilityAbsenceControl);
|
|
333
|
+
const currentModes = new Set(currentObservations.map((o) => o.executionMode));
|
|
334
|
+
const newestCurrent = [...currentObservations].sort((a, b) => {
|
|
335
|
+
if (a.completedAt !== b.completedAt) return a.completedAt - b.completedAt;
|
|
336
|
+
return a.eventId < b.eventId ? -1 : a.eventId > b.eventId ? 1 : 0;
|
|
337
|
+
}).at(-1);
|
|
338
|
+
|
|
339
|
+
let verdict: CompatibilityVerdict = "UNKNOWN";
|
|
340
|
+
if (
|
|
341
|
+
key.evidenceLayer !== "protocol_conformance" &&
|
|
342
|
+
key.evidenceLayer !== "live_route_compatibility" &&
|
|
343
|
+
key.evidenceLayer !== "task_effectiveness"
|
|
344
|
+
) {
|
|
345
|
+
verdict = "UNKNOWN";
|
|
346
|
+
} else if (matchedCapabilityAbsence) {
|
|
347
|
+
verdict = "UNSUPPORTED";
|
|
348
|
+
notes.push("capability_absence_control");
|
|
349
|
+
} else if (currentFails.length > 0) {
|
|
350
|
+
verdict = "DEGRADED";
|
|
351
|
+
} else if (currentPasses.length > 0 && !currentInconclusive && !currentBlocked) {
|
|
352
|
+
if (currentModes.size > 1) {
|
|
353
|
+
verdict = "PROBED";
|
|
354
|
+
notes.push("mixed_execution_modes");
|
|
355
|
+
} else if (key.evidenceLayer === "protocol_conformance" && newestCurrent?.executionMode === "fixture") {
|
|
356
|
+
if (!suiteManifest) {
|
|
357
|
+
verdict = "PROBED";
|
|
358
|
+
notes.push("suite_manifest_unavailable");
|
|
359
|
+
} else {
|
|
360
|
+
const evaluation = evaluateAllApplicableRequiredPassV1(
|
|
361
|
+
suiteManifest,
|
|
362
|
+
ordered,
|
|
363
|
+
newestCurrent.executionMode,
|
|
364
|
+
{
|
|
365
|
+
subject: newestCurrent.subject.subjectKind === "protocol" ? newestCurrent.subject : undefined,
|
|
366
|
+
loadScenarioManifest: opts.loadScenarioManifest,
|
|
367
|
+
loadScenarioRequirements: opts.loadScenarioRequirements,
|
|
368
|
+
asOf,
|
|
369
|
+
},
|
|
370
|
+
);
|
|
371
|
+
notes.push(...evaluation.notes);
|
|
372
|
+
if (evaluation.applicableRequiredScenarioIds.length === 0) {
|
|
373
|
+
verdict = "UNKNOWN";
|
|
374
|
+
} else if (evaluation.canVerify) {
|
|
375
|
+
verdict = "VERIFIED";
|
|
376
|
+
notes.push("all-applicable-required-pass-v1");
|
|
377
|
+
} else {
|
|
378
|
+
verdict = "PROBED";
|
|
379
|
+
notes.push("incomplete_required_coverage");
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
} else if (key.evidenceLayer === "live_route_compatibility" && newestCurrent?.executionMode === "live") {
|
|
383
|
+
if (!suiteManifest) {
|
|
384
|
+
verdict = "PROBED";
|
|
385
|
+
notes.push("suite_manifest_unavailable");
|
|
386
|
+
} else {
|
|
387
|
+
const evaluation = evaluateAllApplicableRequiredPassV1(
|
|
388
|
+
suiteManifest,
|
|
389
|
+
ordered,
|
|
390
|
+
newestCurrent.executionMode,
|
|
391
|
+
{
|
|
392
|
+
subject: newestCurrent.subject.subjectKind === "route" ? newestCurrent.subject : undefined,
|
|
393
|
+
routeSupportedClaims: opts.routeSupportedClaims,
|
|
394
|
+
loadScenarioManifest: opts.loadScenarioManifest,
|
|
395
|
+
loadScenarioRequirements: opts.loadScenarioRequirements,
|
|
396
|
+
asOf,
|
|
397
|
+
},
|
|
398
|
+
);
|
|
399
|
+
notes.push(...evaluation.notes);
|
|
400
|
+
if (evaluation.applicableRequiredScenarioIds.length === 0) {
|
|
401
|
+
verdict = "UNKNOWN";
|
|
402
|
+
} else if (evaluation.canVerify) {
|
|
403
|
+
verdict = "VERIFIED";
|
|
404
|
+
notes.push("all-applicable-required-pass-v1");
|
|
405
|
+
} else {
|
|
406
|
+
verdict = "PROBED";
|
|
407
|
+
notes.push("incomplete_required_coverage");
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
} else {
|
|
411
|
+
verdict = "PROBED";
|
|
412
|
+
}
|
|
413
|
+
} else if (currentPasses.length > 0) {
|
|
414
|
+
verdict = "PROBED";
|
|
415
|
+
} else if (currentBlocked) {
|
|
416
|
+
verdict = "BLOCKED";
|
|
417
|
+
} else {
|
|
418
|
+
verdict = "UNKNOWN";
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
return {
|
|
422
|
+
key,
|
|
423
|
+
verdict,
|
|
424
|
+
asOf,
|
|
425
|
+
scenarioManifestDigests: [...digests].sort(),
|
|
426
|
+
contributingEventIds: contributing,
|
|
427
|
+
contradictingEventIds: contradicting,
|
|
428
|
+
notes,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
export function excludeEventIds(index: InvalidationIndex): Set<string> {
|
|
433
|
+
const out = new Set<string>([...index.purgedEventIds]);
|
|
434
|
+
for (const id of index.invalidatedBy.keys()) out.add(id);
|
|
435
|
+
return out;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
export { isEventExcluded };
|