@fitlab-ai/agent-infra 0.9.6 → 0.9.7
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/dist/bin/internal-cli.js +5 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/app-server.js +45 -4
- package/dist/lib/agent-clients/adapters/codex-lifecycle/build-identity.js +162 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/capability-store.js +244 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.js +370 -0
- package/dist/lib/agent-clients/adapters/codex-lifecycle/store.js +21 -5
- package/dist/lib/defaults.json +10 -2
- package/dist/lib/git/workflow.js +30 -3
- package/dist/lib/internal/codex-lifecycle.js +69 -1
- package/dist/lib/internal/codex-sandbox-controller.js +106 -0
- package/dist/lib/internal/task-orchestration.js +37 -5
- package/dist/lib/platform/context.js +6 -4
- package/dist/lib/platform/github-client.js +55 -2
- package/dist/lib/process-data/checkpoint.js +319 -0
- package/dist/lib/process-data/index.js +128 -60
- package/dist/lib/process-data/materialize.js +84 -0
- package/dist/lib/process-data/normalize.js +34 -1
- package/dist/lib/process-data/reconcile.js +55 -1
- package/dist/lib/process-data/sources.js +422 -1
- package/dist/lib/process-data/store.js +341 -11
- package/dist/lib/sandbox/commands/create.js +103 -51
- package/dist/lib/sandbox/commands/rm.js +64 -15
- package/dist/lib/sandbox/commands/start.js +4 -3
- package/dist/lib/sandbox/control/container-identity.js +64 -0
- package/dist/lib/sandbox/control/executor.js +13 -7
- package/dist/lib/sandbox/control/lifecycle.js +238 -29
- package/dist/lib/sandbox/control/protocol.js +7 -0
- package/dist/lib/sandbox/control/server.js +202 -26
- package/dist/lib/sandbox/control/state.js +25 -15
- package/dist/lib/sandbox/control/timing.js +8 -0
- package/dist/lib/sandbox/mounts.js +5 -13
- package/dist/lib/sandbox/recovery.js +59 -42
- package/dist/lib/sandbox/workspace-view.js +57 -40
- package/dist/lib/server/daemon.js +1 -1
- package/dist/lib/server/process-control.js +11 -1
- package/dist/lib/server/process-state.js +57 -20
- package/dist/lib/task/artifact-lifecycle.js +82 -11
- package/dist/lib/task/artifact-receipts.js +128 -0
- package/dist/lib/task/codex-orchestration.js +81 -6
- package/dist/lib/task/commands/validate.js +1 -1
- package/dist/lib/task/create.js +10 -2
- package/dist/lib/task/delegation-receipts.js +84 -2
- package/dist/lib/task/events.js +139 -2
- package/dist/lib/task/ledger.js +13 -1
- package/dist/lib/task/orchestration.js +235 -14
- package/dist/lib/task/sections.js +5 -1
- package/dist/lib/task/task-execution-lock.js +5 -4
- package/dist/lib/task/verification-engine.js +40 -8
- package/dist/lifecycle-build-manifest.json +6 -0
- package/lib/agent-clients/adapters/codex-lifecycle/app-server.ts +60 -3
- package/lib/agent-clients/adapters/codex-lifecycle/build-identity.ts +227 -0
- package/lib/agent-clients/adapters/codex-lifecycle/capability-store.ts +321 -0
- package/lib/agent-clients/adapters/codex-lifecycle/manifest-files.json +26 -0
- package/lib/agent-clients/adapters/codex-lifecycle/sandbox-controller.ts +453 -0
- package/lib/agent-clients/adapters/codex-lifecycle/store.ts +33 -4
- package/lib/defaults.json +10 -2
- package/lib/git/workflow.ts +25 -2
- package/lib/internal/codex-lifecycle.ts +68 -1
- package/lib/internal/codex-sandbox-controller.ts +109 -0
- package/lib/internal/task-orchestration.ts +31 -4
- package/lib/platform/context.ts +8 -5
- package/lib/platform/github-client.ts +61 -3
- package/lib/process-data/checkpoint.ts +335 -0
- package/lib/process-data/index.ts +112 -60
- package/lib/process-data/materialize.ts +83 -0
- package/lib/process-data/normalize.ts +29 -1
- package/lib/process-data/reconcile.ts +64 -2
- package/lib/process-data/sources.ts +449 -2
- package/lib/process-data/store.ts +304 -11
- package/lib/process-data/types.ts +124 -2
- package/lib/sandbox/commands/create.ts +56 -7
- package/lib/sandbox/commands/rm.ts +64 -14
- package/lib/sandbox/commands/start.ts +4 -3
- package/lib/sandbox/control/container-identity.ts +93 -0
- package/lib/sandbox/control/executor.ts +14 -10
- package/lib/sandbox/control/lifecycle.ts +264 -33
- package/lib/sandbox/control/protocol.ts +31 -5
- package/lib/sandbox/control/server.ts +184 -24
- package/lib/sandbox/control/state.ts +34 -17
- package/lib/sandbox/control/timing.ts +12 -0
- package/lib/sandbox/mounts.ts +5 -15
- package/lib/sandbox/recovery.ts +71 -42
- package/lib/sandbox/workspace-view.ts +61 -40
- package/lib/server/daemon.ts +1 -1
- package/lib/server/process-control.ts +13 -3
- package/lib/server/process-state.ts +82 -27
- package/lib/task/artifact-lifecycle.ts +73 -10
- package/lib/task/artifact-receipts.ts +161 -0
- package/lib/task/codex-orchestration.ts +91 -5
- package/lib/task/commands/validate.ts +1 -1
- package/lib/task/create.ts +11 -2
- package/lib/task/delegation-receipts.ts +160 -8
- package/lib/task/events.ts +122 -1
- package/lib/task/ledger.ts +16 -1
- package/lib/task/orchestration.ts +356 -17
- package/lib/task/sections.ts +6 -2
- package/lib/task/task-execution-lock.ts +11 -9
- package/lib/task/verification-engine.ts +51 -16
- package/package.json +1 -1
- package/templates/.agents/README.en.md +7 -0
- package/templates/.agents/README.zh-CN.md +5 -0
- package/templates/.agents/hooks/lifecycle-delegation.js +25 -1
- package/templates/.agents/rules/lifecycle-orchestration.en.md +8 -2
- package/templates/.agents/rules/lifecycle-orchestration.zh-CN.md +8 -2
- package/templates/.agents/rules/pr-checks-commands.github.en.md +1 -1
- package/templates/.agents/rules/pr-checks-commands.github.zh-CN.md +1 -1
- package/templates/.agents/rules/review-handshake.en.md +3 -1
- package/templates/.agents/rules/review-handshake.zh-CN.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.en.md +3 -1
- package/templates/.agents/skills/code-task/reference/dual-mode.zh-CN.md +3 -1
- package/templates/.agents/skills/complete-task/SKILL.en.md +4 -0
- package/templates/.agents/skills/complete-task/SKILL.zh-CN.md +4 -0
- package/templates/.agents/skills/run-manual-validation/SKILL.en.md +6 -6
- package/templates/.agents/skills/run-manual-validation/SKILL.zh-CN.md +6 -6
- package/templates/.agents/skills/run-manual-validation/config/verify.en.json +14 -3
- package/templates/.agents/skills/run-manual-validation/config/verify.zh-CN.json +14 -3
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.en.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/discovery-and-execution.zh-CN.md +67 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.en.md +25 -0
- package/templates/.agents/skills/run-manual-validation/reference/report-template.zh-CN.md +25 -0
- package/templates/.agents/skills/run-task/SKILL.en.md +9 -7
- package/templates/.agents/skills/run-task/SKILL.zh-CN.md +9 -7
- package/templates/.agents/skills/run-task/reference/host-validation.en.md +2 -0
- package/templates/.agents/skills/run-task/reference/host-validation.zh-CN.md +2 -0
- package/templates/.agents/skills/update-agent-infra/scripts/sync-templates.js +75 -3
- package/templates/.agents/workspace/README.en.md +0 -26
- package/templates/.agents/workspace/README.zh-CN.md +0 -26
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { execFileSync } from 'node:child_process';
|
|
4
3
|
import { detectRepoRoot } from '../task/resolve-ref.js';
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { acquireGitHubCheckpoint, commitGitHubCheckpoint, readGitHubCheckpoint, releaseGitHubCheckpoint } from './checkpoint.js';
|
|
5
|
+
import { materializeJson, materializeSnapshot } from './materialize.js';
|
|
6
|
+
import { normalizeResources } from './normalize.js';
|
|
7
|
+
import { reconcileRecords, reconcileResourceRecords } from './reconcile.js';
|
|
7
8
|
import { readAppliedOverlays, repairSnapshot } from './repair.js';
|
|
8
|
-
import {
|
|
9
|
-
import { canonicalJsonBytes, createObjectStore,
|
|
9
|
+
import { collectGitHubBoundary, resolveGitHubRepository } from './sources.js';
|
|
10
|
+
import { canonicalJsonBytes, createObjectStore, findSnapshot, publishSnapshotV2, verifySnapshot } from './store.js';
|
|
11
|
+
import { createGitHubClient } from '../platform/github-client.js';
|
|
10
12
|
const USAGE = `Usage: ai data <command> [options]
|
|
11
13
|
|
|
12
14
|
Commands:
|
|
13
|
-
capture [--source
|
|
15
|
+
capture [--source github] [--root <dir>] [--full-reconcile]
|
|
14
16
|
verify <snapshot-id> [--root <dir>]
|
|
15
17
|
audit <snapshot-id> [--root <dir>] [--format json|text]
|
|
16
18
|
repair <snapshot-id> [--root <dir>] [--apply]
|
|
17
|
-
export <snapshot-id> [--root <dir>] [--repairs none|applied] [--output <file|->]
|
|
19
|
+
export <snapshot-id> [--root <dir>] [--repairs none|applied] [--as-of <ISO>] [--output <file|->]
|
|
18
20
|
`;
|
|
19
21
|
function parseArgs(args, valueOptions, booleanOptions) {
|
|
20
22
|
const positionals = [];
|
|
@@ -61,14 +63,6 @@ function dataRoot(repoRoot, requested) {
|
|
|
61
63
|
}
|
|
62
64
|
return resolved;
|
|
63
65
|
}
|
|
64
|
-
function repositoryIdentity(repoRoot) {
|
|
65
|
-
try {
|
|
66
|
-
return execFileSync('git', ['config', '--get', 'remote.origin.url'], { cwd: repoRoot, encoding: 'utf8' }).trim() || path.basename(repoRoot);
|
|
67
|
-
}
|
|
68
|
-
catch {
|
|
69
|
-
return path.basename(repoRoot);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
66
|
function writeJson(value) {
|
|
73
67
|
process.stdout.write(`${JSON.stringify(value)}\n`);
|
|
74
68
|
}
|
|
@@ -77,61 +71,127 @@ function optionString(parsed, name) {
|
|
|
77
71
|
return typeof value === 'string' ? value : undefined;
|
|
78
72
|
}
|
|
79
73
|
function capture(args) {
|
|
80
|
-
const parsed = parseArgs(args, new Set(['--source', '--root']), new Set(['--include-excerpts']));
|
|
74
|
+
const parsed = parseArgs(args, new Set(['--source', '--root']), new Set(['--full-reconcile', '--include-excerpts']));
|
|
81
75
|
if (parsed.positionals.length > 0)
|
|
82
76
|
throw new Error('capture does not accept positional arguments');
|
|
83
|
-
const scope = optionString(parsed, '--source') ?? '
|
|
84
|
-
if (
|
|
85
|
-
throw new Error('--source
|
|
77
|
+
const scope = optionString(parsed, '--source') ?? 'github';
|
|
78
|
+
if (scope !== 'github')
|
|
79
|
+
throw new Error('new process-data capture only supports --source github; local data is already normalized and --source all is retired');
|
|
80
|
+
if (parsed.options.has('--include-excerpts'))
|
|
81
|
+
throw new Error('--include-excerpts is not supported for GitHub-only capture');
|
|
86
82
|
const repoRoot = detectRepoRoot();
|
|
87
83
|
const root = dataRoot(repoRoot, optionString(parsed, '--root'));
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
84
|
+
const client = createGitHubClient();
|
|
85
|
+
const repositoryResult = resolveGitHubRepository(repoRoot, client);
|
|
86
|
+
if (!repositoryResult.ok) {
|
|
87
|
+
writeJson({ ok: false, error: repositoryResult.error });
|
|
88
|
+
return 2;
|
|
89
|
+
}
|
|
90
|
+
const repository = repositoryResult.value;
|
|
91
|
+
const leaseResult = acquireGitHubCheckpoint(root, repository);
|
|
92
|
+
if (!leaseResult.ok) {
|
|
93
|
+
writeJson({ ok: false, error: leaseResult.error });
|
|
94
|
+
return 2;
|
|
95
|
+
}
|
|
96
|
+
const lease = leaseResult.value;
|
|
97
|
+
try {
|
|
98
|
+
const checkpointResult = readGitHubCheckpoint(root, repository);
|
|
99
|
+
if (!checkpointResult.ok) {
|
|
100
|
+
writeJson({ ok: false, error: checkpointResult.error });
|
|
96
101
|
return 2;
|
|
97
102
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
const github =
|
|
103
|
+
const checkpoint = checkpointResult.value;
|
|
104
|
+
const full = parsed.options.has('--full-reconcile');
|
|
105
|
+
const observedFrom = new Date().toISOString();
|
|
106
|
+
const github = collectGitHubBoundary(repoRoot, {
|
|
107
|
+
client,
|
|
108
|
+
fromInclusive: full ? null : checkpoint?.watermark ?? null,
|
|
109
|
+
reconciliation: full ? 'full' : 'incremental'
|
|
110
|
+
});
|
|
102
111
|
if (!github.ok) {
|
|
103
112
|
writeJson({ ok: false, error: github.error });
|
|
104
113
|
return 2;
|
|
105
114
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
115
|
+
const store = createObjectStore(root);
|
|
116
|
+
for (const object of github.value.objects) {
|
|
117
|
+
if (object.content !== undefined && object.disposition?.state === 'included') {
|
|
118
|
+
const stored = store.put(Buffer.from(object.content, 'utf8'));
|
|
119
|
+
if (stored.sha256 !== object.sha256)
|
|
120
|
+
throw new Error(`source hash mismatch: ${object.sourceIdentity}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
let parent = [];
|
|
124
|
+
if (checkpoint) {
|
|
125
|
+
const parentPath = findSnapshot(root, checkpoint.snapshotId);
|
|
126
|
+
if (!parentPath)
|
|
127
|
+
throw new Error(`checkpoint head snapshot is missing: ${checkpoint.snapshotId}`);
|
|
128
|
+
const materialized = materializeSnapshot(root, checkpoint.snapshotId);
|
|
129
|
+
if (!materialized.manifest || materialized.manifest.manifestSha256 !== checkpoint.manifestSha256 || materialized.manifest.watermark !== checkpoint.watermark) {
|
|
130
|
+
throw new Error('checkpoint does not match its verified snapshot head');
|
|
131
|
+
}
|
|
132
|
+
parent = materialized.records;
|
|
116
133
|
}
|
|
134
|
+
const current = normalizeResources(github.value.objects);
|
|
135
|
+
const reconciliation = reconcileResourceRecords(current, {
|
|
136
|
+
parent,
|
|
137
|
+
full,
|
|
138
|
+
deferred: github.value.deferred,
|
|
139
|
+
unavailable: github.value.unavailable
|
|
140
|
+
});
|
|
141
|
+
const quality = reconcileRecords(reconciliation.records, 'github');
|
|
142
|
+
const fromInclusive = checkpoint?.watermark ?? null;
|
|
143
|
+
const hasStrictSinceEndpoint = github.value.endpoints.some((endpoint) => endpoint.queryMode === 'strict-since');
|
|
144
|
+
const queryAfter = !full && fromInclusive && hasStrictSinceEndpoint
|
|
145
|
+
? new Date(new Date(fromInclusive).getTime() - 1000).toISOString()
|
|
146
|
+
: null;
|
|
147
|
+
const responseDates = github.value.responseDates;
|
|
148
|
+
const published = publishSnapshotV2(root, {
|
|
149
|
+
scope: 'github',
|
|
150
|
+
repository,
|
|
151
|
+
observedFrom,
|
|
152
|
+
observedTo: github.value.watermark,
|
|
153
|
+
objects: github.value.objects.map(({ content: _content, ...object }) => object),
|
|
154
|
+
endpoints: github.value.endpoints,
|
|
155
|
+
excerptsEnabled: false,
|
|
156
|
+
records: reconciliation.records,
|
|
157
|
+
quality: quality.findings,
|
|
158
|
+
repairs: quality.repairs,
|
|
159
|
+
snapshotKind: checkpoint ? 'delta' : 'base',
|
|
160
|
+
parentSnapshotId: checkpoint?.snapshotId ?? null,
|
|
161
|
+
checkpointBefore: checkpoint?.snapshotId ?? null,
|
|
162
|
+
watermark: github.value.watermark,
|
|
163
|
+
window: { fromInclusive, queryAfter, toExclusive: github.value.watermark, precision: 'second' },
|
|
164
|
+
observation: {
|
|
165
|
+
cutoffSource: 'github-response-date',
|
|
166
|
+
preflightDate: github.value.preflightDate,
|
|
167
|
+
responseDates
|
|
168
|
+
},
|
|
169
|
+
coverage: { mode: 'boundary-reread-with-full-reconcile', absoluteCompleteness: false },
|
|
170
|
+
reconciliation: full ? 'full' : 'incremental',
|
|
171
|
+
operations: reconciliation.operations
|
|
172
|
+
});
|
|
173
|
+
const verified = verifySnapshot(root, published.snapshotId);
|
|
174
|
+
if (!verified.ok || !verified.manifest || verified.manifest.schema !== 'raw-manifest/v2') {
|
|
175
|
+
throw new Error(`published snapshot failed verification: ${verified.errors.join('; ')}`);
|
|
176
|
+
}
|
|
177
|
+
const committed = commitGitHubCheckpoint(lease, {
|
|
178
|
+
schema: 'github-checkpoint/v1',
|
|
179
|
+
repository,
|
|
180
|
+
snapshotId: published.snapshotId,
|
|
181
|
+
watermark: github.value.watermark,
|
|
182
|
+
manifestSha256: verified.manifest.manifestSha256,
|
|
183
|
+
committedAt: new Date().toISOString()
|
|
184
|
+
});
|
|
185
|
+
if (!committed.ok) {
|
|
186
|
+
writeJson({ ok: false, error: committed.error });
|
|
187
|
+
return 2;
|
|
188
|
+
}
|
|
189
|
+
writeJson({ ok: true, snapshotId: published.snapshotId, scope, snapshotKind: checkpoint ? 'delta' : 'base', reconciliation: full ? 'full' : 'incremental', created: published.created, objects: github.value.objects.length, records: reconciliation.records.length, watermark: github.value.watermark, deferred: github.value.deferred.length });
|
|
190
|
+
return 0;
|
|
191
|
+
}
|
|
192
|
+
finally {
|
|
193
|
+
releaseGitHubCheckpoint(lease);
|
|
117
194
|
}
|
|
118
|
-
const records = normalizeObjects(objects);
|
|
119
|
-
const reconciliation = reconcileRecords(records, scope);
|
|
120
|
-
const observedTo = new Date().toISOString();
|
|
121
|
-
const published = publishSnapshot(root, {
|
|
122
|
-
scope: scope,
|
|
123
|
-
repository,
|
|
124
|
-
observedFrom,
|
|
125
|
-
observedTo,
|
|
126
|
-
objects: objects.map(({ content: _content, ...object }) => object),
|
|
127
|
-
endpoints,
|
|
128
|
-
excerptsEnabled: parsed.options.has('--include-excerpts'),
|
|
129
|
-
records,
|
|
130
|
-
quality: reconciliation.findings,
|
|
131
|
-
repairs: reconciliation.repairs
|
|
132
|
-
});
|
|
133
|
-
writeJson({ ok: true, snapshotId: published.snapshotId, scope, created: published.created, objects: objects.length, records: records.length });
|
|
134
|
-
return 0;
|
|
135
195
|
}
|
|
136
196
|
function verify(args) {
|
|
137
197
|
const parsed = parseArgs(args, new Set(['--root']), new Set());
|
|
@@ -182,7 +242,7 @@ function repair(args) {
|
|
|
182
242
|
return 0;
|
|
183
243
|
}
|
|
184
244
|
function exportSnapshot(args) {
|
|
185
|
-
const parsed = parseArgs(args, new Set(['--root', '--repairs', '--output']), new Set());
|
|
245
|
+
const parsed = parseArgs(args, new Set(['--root', '--repairs', '--output', '--as-of']), new Set());
|
|
186
246
|
if (parsed.positionals.length !== 1)
|
|
187
247
|
throw new Error('export requires one snapshot id');
|
|
188
248
|
const repairMode = optionString(parsed, '--repairs') ?? 'none';
|
|
@@ -196,7 +256,15 @@ function exportSnapshot(args) {
|
|
|
196
256
|
writeJson(verified);
|
|
197
257
|
return 1;
|
|
198
258
|
}
|
|
199
|
-
let content
|
|
259
|
+
let content;
|
|
260
|
+
if (verified.manifest?.schema === 'raw-manifest/v2') {
|
|
261
|
+
content = materializeJson(root, parsed.positionals[0], { asOf: optionString(parsed, '--as-of') });
|
|
262
|
+
}
|
|
263
|
+
else {
|
|
264
|
+
if (optionString(parsed, '--as-of'))
|
|
265
|
+
throw new Error('--as-of is only supported for v2 GitHub snapshots');
|
|
266
|
+
content = fs.readFileSync(path.join(verified.snapshotPath, 'normalized.jsonl'), 'utf8');
|
|
267
|
+
}
|
|
200
268
|
if (repairMode === 'applied')
|
|
201
269
|
content += readAppliedOverlays(root, parsed.positionals[0]);
|
|
202
270
|
if (output === '-')
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { findSnapshot, verifySnapshot } from './store.js';
|
|
4
|
+
function readRecords(snapshotPath, schema) {
|
|
5
|
+
const file = path.join(snapshotPath, 'normalized.jsonl');
|
|
6
|
+
if (!fs.existsSync(file))
|
|
7
|
+
return [];
|
|
8
|
+
return fs.readFileSync(file, 'utf8').split('\n').filter(Boolean).flatMap((line) => {
|
|
9
|
+
const value = JSON.parse(line);
|
|
10
|
+
if (value.schema !== schema)
|
|
11
|
+
return [];
|
|
12
|
+
const { schema: _schema, ...record } = value;
|
|
13
|
+
return [record];
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
function materializeSnapshot(root, snapshotId, options = {}) {
|
|
17
|
+
const target = options.asOf ? new Date(options.asOf) : null;
|
|
18
|
+
if (target && !Number.isFinite(target.getTime()))
|
|
19
|
+
throw new Error('as-of must be an ISO timestamp');
|
|
20
|
+
const chain = [];
|
|
21
|
+
let currentId = snapshotId;
|
|
22
|
+
let repository = null;
|
|
23
|
+
while (currentId) {
|
|
24
|
+
const currentPath = findSnapshot(root, currentId);
|
|
25
|
+
if (!currentPath)
|
|
26
|
+
throw new Error(`snapshot not found: ${currentId}`);
|
|
27
|
+
const verified = verifySnapshot(root, currentId);
|
|
28
|
+
if (!verified.ok || !verified.manifest)
|
|
29
|
+
throw new Error(`snapshot is not verified: ${currentId}`);
|
|
30
|
+
if (verified.manifest.schema !== 'raw-manifest/v2') {
|
|
31
|
+
if (chain.length > 0)
|
|
32
|
+
throw new Error('v1 and v2 snapshots cannot share a lineage');
|
|
33
|
+
return { snapshotId, records: readRecords(currentPath, 'normalized/v1'), manifest: null };
|
|
34
|
+
}
|
|
35
|
+
const manifest = verified.manifest;
|
|
36
|
+
if (repository && manifest.repository !== repository)
|
|
37
|
+
throw new Error('snapshot lineage repository mismatch');
|
|
38
|
+
repository = manifest.repository;
|
|
39
|
+
chain.push({ id: currentId, path: currentPath, manifest });
|
|
40
|
+
currentId = manifest.parentSnapshotId;
|
|
41
|
+
}
|
|
42
|
+
if (chain.length === 0)
|
|
43
|
+
throw new Error('snapshot lineage is empty');
|
|
44
|
+
const chronological = [...chain].reverse();
|
|
45
|
+
for (let index = 1; index < chronological.length; index += 1) {
|
|
46
|
+
if (new Date(chronological[index - 1].manifest.watermark).getTime() >= new Date(chronological[index].manifest.watermark).getTime()) {
|
|
47
|
+
throw new Error('snapshot watermark is not monotonic');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const view = new Map();
|
|
51
|
+
let appliedManifest = null;
|
|
52
|
+
for (const entry of chronological) {
|
|
53
|
+
if (target && new Date(entry.manifest.watermark).getTime() > target.getTime())
|
|
54
|
+
continue;
|
|
55
|
+
appliedManifest = entry.manifest;
|
|
56
|
+
for (const record of readRecords(entry.path, 'normalized/v2')) {
|
|
57
|
+
const identity = record.resourceIdentity ?? record.sourceIdentity;
|
|
58
|
+
switch (record.operation) {
|
|
59
|
+
case 'tombstone':
|
|
60
|
+
view.delete(identity);
|
|
61
|
+
break;
|
|
62
|
+
case 'unavailable':
|
|
63
|
+
view.set(identity, record);
|
|
64
|
+
break;
|
|
65
|
+
default:
|
|
66
|
+
view.set(identity, { ...record, operation: undefined });
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (target && !appliedManifest)
|
|
72
|
+
throw new Error('no verified snapshot is available at the requested as-of time');
|
|
73
|
+
return {
|
|
74
|
+
snapshotId,
|
|
75
|
+
records: [...view.values()].sort((left, right) => left.recordId.localeCompare(right.recordId)),
|
|
76
|
+
manifest: appliedManifest
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
function materializeJson(root, snapshotId, options = {}) {
|
|
80
|
+
const result = materializeSnapshot(root, snapshotId, options);
|
|
81
|
+
return result.records.map((record) => JSON.stringify({ schema: 'normalized/v2', ...record })).join('\n') + (result.records.length ? '\n' : '');
|
|
82
|
+
}
|
|
83
|
+
export { materializeJson, materializeSnapshot };
|
|
84
|
+
//# sourceMappingURL=materialize.js.map
|
|
@@ -194,5 +194,38 @@ function normalizeObjects(objects) {
|
|
|
194
194
|
}
|
|
195
195
|
return records.sort((left, right) => left.recordId.localeCompare(right.recordId));
|
|
196
196
|
}
|
|
197
|
-
|
|
197
|
+
function normalizeResources(objects) {
|
|
198
|
+
const records = [];
|
|
199
|
+
for (const object of objects) {
|
|
200
|
+
if (object.role !== 'resource')
|
|
201
|
+
continue;
|
|
202
|
+
const identity = object.resourceIdentity ?? object.sourceIdentity;
|
|
203
|
+
let data = { availability: 'unknown' };
|
|
204
|
+
try {
|
|
205
|
+
data = JSON.parse(object.content ?? '{}');
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
data = { availability: 'unavailable', reason: 'invalid resource JSON' };
|
|
209
|
+
}
|
|
210
|
+
const binding = typeof data === 'object' && data !== null && !Array.isArray(data) && typeof data.body === 'string'
|
|
211
|
+
? data.body.match(TASK_ID_RE)?.[0]
|
|
212
|
+
: undefined;
|
|
213
|
+
records.push({
|
|
214
|
+
recordId: recordId('platform-resource', identity),
|
|
215
|
+
kind: 'platform-resource',
|
|
216
|
+
sourceIdentity: identity,
|
|
217
|
+
resourceIdentity: identity,
|
|
218
|
+
sourceSha256: object.sha256,
|
|
219
|
+
...(binding ? { binding } : {}),
|
|
220
|
+
...(object.eventTime ? { observedAt: { state: 'known', value: object.eventTime } } : {}),
|
|
221
|
+
...(object.parentIdentity ? { parentIdentity: object.parentIdentity } : {}),
|
|
222
|
+
...(object.pageSha256 ? {
|
|
223
|
+
evidence: [{ resourceIdentity: identity, resourceSha256: object.sha256, pageSha256: object.pageSha256 }]
|
|
224
|
+
} : {}),
|
|
225
|
+
data
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
return records.sort((left, right) => left.recordId.localeCompare(right.recordId));
|
|
229
|
+
}
|
|
230
|
+
export { normalizeObjects, normalizeResources };
|
|
198
231
|
//# sourceMappingURL=normalize.js.map
|
|
@@ -91,5 +91,59 @@ function reconcileRecords(records, scope) {
|
|
|
91
91
|
repairs: repairs.sort((left, right) => left.repairId.localeCompare(right.repairId))
|
|
92
92
|
};
|
|
93
93
|
}
|
|
94
|
-
|
|
94
|
+
function reconcileResourceRecords(current, options) {
|
|
95
|
+
const parent = new Map((options.parent ?? []).filter((record) => record.resourceIdentity).map((record) => [record.resourceIdentity, record]));
|
|
96
|
+
const currentMap = new Map(current.filter((record) => record.resourceIdentity).map((record) => [record.resourceIdentity, record]));
|
|
97
|
+
const deferred = new Set(options.deferred ?? []);
|
|
98
|
+
const unavailable = new Set(options.unavailable ?? []);
|
|
99
|
+
const records = [];
|
|
100
|
+
const operations = { upsert: 0, supersede: 0, tombstone: 0, unavailable: 0 };
|
|
101
|
+
for (const record of currentMap.values()) {
|
|
102
|
+
const identity = record.resourceIdentity;
|
|
103
|
+
const previous = parent.get(identity);
|
|
104
|
+
if (!previous) {
|
|
105
|
+
records.push({ ...record, operation: 'upsert' });
|
|
106
|
+
operations.upsert += 1;
|
|
107
|
+
}
|
|
108
|
+
else if (previous.sourceSha256 !== record.sourceSha256) {
|
|
109
|
+
records.push({ ...record, operation: 'supersede' });
|
|
110
|
+
operations.supersede += 1;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (options.full) {
|
|
114
|
+
for (const [identity, previous] of parent) {
|
|
115
|
+
if (currentMap.has(identity) || deferred.has(identity) || unavailable.has(identity)
|
|
116
|
+
|| (previous.parentIdentity && deferred.has(previous.parentIdentity))
|
|
117
|
+
|| (previous.parentIdentity && unavailable.has(previous.parentIdentity)))
|
|
118
|
+
continue;
|
|
119
|
+
records.push({
|
|
120
|
+
recordId: previous.recordId,
|
|
121
|
+
kind: 'platform-resource',
|
|
122
|
+
sourceIdentity: identity,
|
|
123
|
+
resourceIdentity: identity,
|
|
124
|
+
sourceSha256: previous.sourceSha256,
|
|
125
|
+
operation: 'tombstone',
|
|
126
|
+
data: { availability: 'tombstone', previousSha256: previous.sourceSha256 }
|
|
127
|
+
});
|
|
128
|
+
operations.tombstone += 1;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
for (const identity of unavailable) {
|
|
132
|
+
if (!parent.has(identity) && !currentMap.has(identity))
|
|
133
|
+
continue;
|
|
134
|
+
const previous = parent.get(identity) ?? currentMap.get(identity);
|
|
135
|
+
records.push({
|
|
136
|
+
recordId: previous.recordId,
|
|
137
|
+
kind: 'platform-resource',
|
|
138
|
+
sourceIdentity: identity,
|
|
139
|
+
resourceIdentity: identity,
|
|
140
|
+
sourceSha256: previous.sourceSha256,
|
|
141
|
+
operation: 'unavailable',
|
|
142
|
+
data: { availability: 'unavailable', reason: 'source-unavailable' }
|
|
143
|
+
});
|
|
144
|
+
operations.unavailable += 1;
|
|
145
|
+
}
|
|
146
|
+
return { records: records.sort((left, right) => left.recordId.localeCompare(right.recordId)), operations };
|
|
147
|
+
}
|
|
148
|
+
export { reconcileRecords, reconcileResourceRecords };
|
|
95
149
|
//# sourceMappingURL=reconcile.js.map
|