@outcomeeng/spx 0.6.11 → 0.6.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/dist/cli.js CHANGED
@@ -1787,6 +1787,7 @@ import {
1787
1787
  mkdir as nodeMkdir,
1788
1788
  open as nodeOpen,
1789
1789
  readdir as nodeReaddir,
1790
+ rename as nodeRename,
1790
1791
  rm as nodeRm
1791
1792
  } from "fs/promises";
1792
1793
  import { dirname as dirname2, join as join2 } from "path";
@@ -1880,6 +1881,9 @@ var defaultFileSystem = {
1880
1881
  },
1881
1882
  readdir: nodeReaddir,
1882
1883
  lstat: nodeLstat,
1884
+ rename: async (from, to) => {
1885
+ await nodeRename(from, to);
1886
+ },
1883
1887
  rm: async (path7, options) => {
1884
1888
  await nodeRm(path7, options);
1885
1889
  }
@@ -1972,8 +1976,8 @@ function formatRunTimestamp(date) {
1972
1976
  );
1973
1977
  return `${year}-${month}-${day}${RUN_TIMESTAMP_SEPARATOR}${hours}-${minutes}-${seconds}-${milliseconds}`;
1974
1978
  }
1975
- function generateRunId(randomBytes2 = nodeRandomBytes) {
1976
- return randomBytes2(STATE_STORE_RUN_TOKEN.ID_BYTES).toString(HEX_ENCODING);
1979
+ function generateRunId(randomBytes3 = nodeRandomBytes) {
1980
+ return randomBytes3(STATE_STORE_RUN_TOKEN.ID_BYTES).toString(HEX_ENCODING);
1977
1981
  }
1978
1982
  function createStateStoreRunToken(options) {
1979
1983
  const startedAt = formatRunTimestamp(options.date);
@@ -1995,7 +1999,7 @@ async function createJsonlRunFile(scopeDir, domainName, options = {}) {
1995
1999
  if (!domainRunsDir.ok) return domainRunsDir;
1996
2000
  const maxAttempts = options.maxAttempts ?? RUN_FILE_CREATE_ATTEMPTS;
1997
2001
  const startedDate = (options.now ?? (() => /* @__PURE__ */ new Date()))();
1998
- const randomBytes2 = options.randomBytes ?? nodeRandomBytes;
2002
+ const randomBytes3 = options.randomBytes ?? nodeRandomBytes;
1999
2003
  try {
2000
2004
  await fs8.mkdir(domainRunsDir.value, { recursive: true });
2001
2005
  } catch (error) {
@@ -2005,7 +2009,7 @@ async function createJsonlRunFile(scopeDir, domainName, options = {}) {
2005
2009
  };
2006
2010
  }
2007
2011
  for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
2008
- const token = createStateStoreRunToken({ date: startedDate, randomBytes: randomBytes2 });
2012
+ const token = createStateStoreRunToken({ date: startedDate, randomBytes: randomBytes3 });
2009
2013
  const name = runFileName(token.runToken);
2010
2014
  const path7 = join2(domainRunsDir.value, name);
2011
2015
  try {
@@ -2528,8 +2532,8 @@ import path2 from "path";
2528
2532
  // src/lib/atomic-file-write.ts
2529
2533
  var TEMP_TOKEN_BYTES = 8;
2530
2534
  var TEMP_SUFFIX = ".tmp";
2531
- function atomicWriteTempPath(targetPath, randomBytes2) {
2532
- const token = randomBytes2(TEMP_TOKEN_BYTES).toString("hex");
2535
+ function atomicWriteTempPath(targetPath, randomBytes3) {
2536
+ const token = randomBytes3(TEMP_TOKEN_BYTES).toString("hex");
2533
2537
  return `${targetPath}.${token}${TEMP_SUFFIX}`;
2534
2538
  }
2535
2539
  async function writeFileAtomic(targetPath, content, options) {
@@ -3223,37 +3227,37 @@ function validateAgents(raw) {
3223
3227
  }
3224
3228
  return { ok: true, value: agents };
3225
3229
  }
3226
- function validateAgentConfig(path7, raw, defaults5) {
3230
+ function validateAgentConfig(path7, raw, defaults6) {
3227
3231
  if (!isRecord2(raw)) return { ok: false, error: `${path7} must be an object` };
3228
3232
  const unknown = rejectUnknownFields(path7, raw, HARNESS_ENVIRONMENT_AGENT_CONFIG_ALLOWED_FIELDS);
3229
3233
  if (!unknown.ok) return unknown;
3230
3234
  const enabledRaw = raw[HARNESS_ENVIRONMENT_CONFIG_FIELDS.ENABLED];
3231
- const enabled = enabledRaw === void 0 ? { ok: true, value: defaults5.enabled } : validateBoolean(`${path7}.${HARNESS_ENVIRONMENT_CONFIG_FIELDS.ENABLED}`, enabledRaw);
3235
+ const enabled = enabledRaw === void 0 ? { ok: true, value: defaults6.enabled } : validateBoolean(`${path7}.${HARNESS_ENVIRONMENT_CONFIG_FIELDS.ENABLED}`, enabledRaw);
3232
3236
  if (!enabled.ok) return enabled;
3233
3237
  const hooksRaw = raw[HARNESS_ENVIRONMENT_CONFIG_FIELDS.HOOKS];
3234
- const hooks = hooksRaw === void 0 ? { ok: true, value: defaults5.hooks } : validateAgentHooks(`${path7}.${HARNESS_ENVIRONMENT_CONFIG_FIELDS.HOOKS}`, hooksRaw, defaults5.hooks);
3238
+ const hooks = hooksRaw === void 0 ? { ok: true, value: defaults6.hooks } : validateAgentHooks(`${path7}.${HARNESS_ENVIRONMENT_CONFIG_FIELDS.HOOKS}`, hooksRaw, defaults6.hooks);
3235
3239
  if (!hooks.ok) return hooks;
3236
3240
  return { ok: true, value: { enabled: enabled.value, hooks: hooks.value } };
3237
3241
  }
3238
- function validateAgentHooks(path7, raw, defaults5) {
3242
+ function validateAgentHooks(path7, raw, defaults6) {
3239
3243
  if (!isRecord2(raw)) return { ok: false, error: `${path7} must be an object` };
3240
3244
  const unknown = rejectUnknownFields(path7, raw, HARNESS_ENVIRONMENT_AGENT_HOOKS_ALLOWED_FIELDS);
3241
3245
  if (!unknown.ok) return unknown;
3242
3246
  const sessionStartRaw = raw[HARNESS_ENVIRONMENT_CONFIG_FIELDS.SESSION_START];
3243
- const sessionStart = sessionStartRaw === void 0 ? { ok: true, value: defaults5.sessionStart } : validateSessionStartHooks(
3247
+ const sessionStart = sessionStartRaw === void 0 ? { ok: true, value: defaults6.sessionStart } : validateSessionStartHooks(
3244
3248
  `${path7}.${HARNESS_ENVIRONMENT_CONFIG_FIELDS.SESSION_START}`,
3245
3249
  sessionStartRaw,
3246
- defaults5.sessionStart
3250
+ defaults6.sessionStart
3247
3251
  );
3248
3252
  if (!sessionStart.ok) return sessionStart;
3249
3253
  return { ok: true, value: { sessionStart: sessionStart.value } };
3250
3254
  }
3251
- function validateSessionStartHooks(path7, raw, defaults5) {
3255
+ function validateSessionStartHooks(path7, raw, defaults6) {
3252
3256
  if (!isRecord2(raw)) return { ok: false, error: `${path7} must be an object` };
3253
3257
  const unknown = rejectUnknownFields(path7, raw, HARNESS_ENVIRONMENT_SESSION_START_HOOKS_ALLOWED_FIELDS);
3254
3258
  if (!unknown.ok) return unknown;
3255
3259
  const compactStdoutRaw = raw[HARNESS_ENVIRONMENT_CONFIG_FIELDS.COMPACT_STDOUT];
3256
- const compactStdout = compactStdoutRaw === void 0 ? { ok: true, value: defaults5.compactStdout } : validateBoolean(`${path7}.${HARNESS_ENVIRONMENT_CONFIG_FIELDS.COMPACT_STDOUT}`, compactStdoutRaw);
3260
+ const compactStdout = compactStdoutRaw === void 0 ? { ok: true, value: defaults6.compactStdout } : validateBoolean(`${path7}.${HARNESS_ENVIRONMENT_CONFIG_FIELDS.COMPACT_STDOUT}`, compactStdoutRaw);
3257
3261
  if (!compactStdout.ok) return compactStdout;
3258
3262
  return { ok: true, value: { compactStdout: compactStdout.value } };
3259
3263
  }
@@ -3326,8 +3330,8 @@ function validatePluginBootstrap(raw) {
3326
3330
  }
3327
3331
  };
3328
3332
  }
3329
- function validateEntryArray(path7, raw, validateEntry, defaults5) {
3330
- if (raw === void 0) return { ok: true, value: defaults5 };
3333
+ function validateEntryArray(path7, raw, validateEntry, defaults6) {
3334
+ if (raw === void 0) return { ok: true, value: defaults6 };
3331
3335
  if (!Array.isArray(raw)) return { ok: false, error: `${path7} must be an array` };
3332
3336
  const entries = [];
3333
3337
  for (const [index, entry] of raw.entries()) {
@@ -3618,6 +3622,36 @@ var diagnoseConfigDescriptor = {
3618
3622
  validate: validate2
3619
3623
  };
3620
3624
 
3625
+ // src/lib/agent-run-journal/config.ts
3626
+ var RUNTIME_SECTION = "runtime";
3627
+ var RUNTIME_CONFIG_FIELDS = {
3628
+ EVENT_NAMESPACE: "eventNamespace"
3629
+ };
3630
+ var RUNTIME_EVENT_NAMESPACE_DEFAULT = "sh.spx";
3631
+ var defaults2 = { eventNamespace: RUNTIME_EVENT_NAMESPACE_DEFAULT };
3632
+ function validate3(value) {
3633
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
3634
+ return { ok: false, error: `${RUNTIME_SECTION} section must be an object` };
3635
+ }
3636
+ const candidate = value;
3637
+ const raw = candidate[RUNTIME_CONFIG_FIELDS.EVENT_NAMESPACE];
3638
+ if (raw === void 0) {
3639
+ return { ok: true, value: { eventNamespace: RUNTIME_EVENT_NAMESPACE_DEFAULT } };
3640
+ }
3641
+ if (typeof raw !== "string" || raw.trim().length === 0) {
3642
+ return {
3643
+ ok: false,
3644
+ error: `${RUNTIME_SECTION}.${RUNTIME_CONFIG_FIELDS.EVENT_NAMESPACE} must be a non-empty string`
3645
+ };
3646
+ }
3647
+ return { ok: true, value: { eventNamespace: raw } };
3648
+ }
3649
+ var runtimeConfigDescriptor = {
3650
+ section: RUNTIME_SECTION,
3651
+ defaults: defaults2,
3652
+ validate: validate3
3653
+ };
3654
+
3621
3655
  // src/lib/file-inclusion/adapters/eslint.ts
3622
3656
  function eslintAdapter(scope2, config) {
3623
3657
  return scope2.excluded.flatMap((entry) => [config.ignoreFlag, entry.path]);
@@ -3678,7 +3712,7 @@ var DEFAULT_SCOPE_CONFIG = {};
3678
3712
  var DEFAULT_TOOLS_CONFIG = Object.fromEntries(
3679
3713
  Object.entries(TOOL_DEFAULT_FLAGS).map(([name, flag]) => [name, { ignoreFlag: flag }])
3680
3714
  );
3681
- var defaults2 = {
3715
+ var defaults3 = {
3682
3716
  scope: DEFAULT_SCOPE_CONFIG,
3683
3717
  tools: DEFAULT_TOOLS_CONFIG
3684
3718
  };
@@ -3750,7 +3784,7 @@ function validateTools(raw) {
3750
3784
  }
3751
3785
  return { ok: true, value: tools };
3752
3786
  }
3753
- function validate3(value) {
3787
+ function validate4(value) {
3754
3788
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
3755
3789
  return { ok: false, error: `${FILE_INCLUSION_SECTION} section must be an object` };
3756
3790
  }
@@ -3777,8 +3811,8 @@ function validate3(value) {
3777
3811
  }
3778
3812
  var fileInclusionConfigDescriptor = {
3779
3813
  section: FILE_INCLUSION_SECTION,
3780
- defaults: defaults2,
3781
- validate: validate3
3814
+ defaults: defaults3,
3815
+ validate: validate4
3782
3816
  };
3783
3817
 
3784
3818
  // src/config/source-roots.ts
@@ -3801,7 +3835,7 @@ var PRECOMMIT_DEFAULTS = {
3801
3835
  sourceDirs: TEST_RELEVANT_SOURCE_ROOT_PREFIXES,
3802
3836
  testPattern: ".test.ts"
3803
3837
  };
3804
- function validate4(value) {
3838
+ function validate5(value) {
3805
3839
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
3806
3840
  return { ok: false, error: `${PRECOMMIT_SECTION} section must be an object` };
3807
3841
  }
@@ -3831,7 +3865,7 @@ function validate4(value) {
3831
3865
  var precommitConfigDescriptor = {
3832
3866
  section: PRECOMMIT_SECTION,
3833
3867
  defaults: PRECOMMIT_DEFAULTS,
3834
- validate: validate4
3868
+ validate: validate5
3835
3869
  };
3836
3870
 
3837
3871
  // src/lib/spec-tree/config.ts
@@ -4022,7 +4056,7 @@ function buildConfigFromKindNames(kindNames) {
4022
4056
  const entries = kindNames.map((kind) => [kind, KIND_REGISTRY[kind]]);
4023
4057
  return { kinds: Object.fromEntries(entries) };
4024
4058
  }
4025
- function validate5(value) {
4059
+ function validate6(value) {
4026
4060
  if (typeof value !== "object" || value === null) {
4027
4061
  return { ok: false, error: `${SPEC_TREE_SECTION} section must be an object` };
4028
4062
  }
@@ -4121,7 +4155,7 @@ function validateKindDefinitionMap(kindEntries) {
4121
4155
  var specTreeConfigDescriptor = {
4122
4156
  section: SPEC_TREE_SECTION,
4123
4157
  defaults: buildDefaults(),
4124
- validate: validate5
4158
+ validate: validate6
4125
4159
  };
4126
4160
 
4127
4161
  // src/config/primitives/path-filter.ts
@@ -4194,7 +4228,7 @@ var TESTING_CONFIG_FIELDS = {
4194
4228
  PASSING_SCOPE: "passingScope"
4195
4229
  };
4196
4230
  var DEFAULT_PASSING_SCOPE = resolveDefaultPassingScope();
4197
- var defaults3 = {
4231
+ var defaults4 = {
4198
4232
  passingScope: DEFAULT_PASSING_SCOPE
4199
4233
  };
4200
4234
  function resolveDefaultPassingScope() {
@@ -4207,14 +4241,14 @@ function resolveDefaultPassingScope() {
4207
4241
  }
4208
4242
  return result.value;
4209
4243
  }
4210
- function validate6(value) {
4244
+ function validate7(value) {
4211
4245
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
4212
4246
  return { ok: false, error: `${TESTING_SECTION} section must be an object` };
4213
4247
  }
4214
4248
  const candidate = value;
4215
4249
  const passingScopeRaw = candidate[TESTING_CONFIG_FIELDS.PASSING_SCOPE];
4216
4250
  if (passingScopeRaw === void 0) {
4217
- return { ok: true, value: defaults3 };
4251
+ return { ok: true, value: defaults4 };
4218
4252
  }
4219
4253
  const passingScopeResult = validatePathFilterConfig(
4220
4254
  passingScopeRaw,
@@ -4230,8 +4264,8 @@ function validate6(value) {
4230
4264
  }
4231
4265
  var testingConfigDescriptor = {
4232
4266
  section: TESTING_SECTION,
4233
- defaults: defaults3,
4234
- validate: validate6
4267
+ defaults: defaults4,
4268
+ validate: validate7
4235
4269
  };
4236
4270
 
4237
4271
  // src/validation/literal/config.ts
@@ -4295,7 +4329,7 @@ var LITERAL_DEFAULTS = {
4295
4329
  function isNonNegativeInteger(value) {
4296
4330
  return typeof value === "number" && Number.isInteger(value) && value >= 0;
4297
4331
  }
4298
- function validate7(value) {
4332
+ function validate8(value) {
4299
4333
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
4300
4334
  return { ok: false, error: `${LITERAL_SECTION} section must be an object` };
4301
4335
  }
@@ -4359,7 +4393,7 @@ function isStringArray(value) {
4359
4393
  var literalConfigDescriptor = {
4360
4394
  section: LITERAL_SECTION,
4361
4395
  defaults: LITERAL_DEFAULTS,
4362
- validate: validate7
4396
+ validate: validate8
4363
4397
  };
4364
4398
 
4365
4399
  // src/validation/config/descriptor.ts
@@ -4378,7 +4412,7 @@ var VALIDATION_PATH_TOOL_SUBSECTIONS = {
4378
4412
  LITERAL: "literal",
4379
4413
  FORMATTING: "formatting"
4380
4414
  };
4381
- var defaults4 = {
4415
+ var defaults5 = {
4382
4416
  paths: {},
4383
4417
  literal: {
4384
4418
  enabled: true,
@@ -4418,7 +4452,7 @@ function validateLiteral(raw) {
4418
4452
  };
4419
4453
  }
4420
4454
  const candidate = raw;
4421
- const enabledRaw = candidate[VALIDATION_ENABLED_FIELD] ?? defaults4.literal.enabled;
4455
+ const enabledRaw = candidate[VALIDATION_ENABLED_FIELD] ?? defaults5.literal.enabled;
4422
4456
  if (typeof enabledRaw !== "boolean") {
4423
4457
  return {
4424
4458
  ok: false,
@@ -4443,7 +4477,7 @@ function validateKnip(raw) {
4443
4477
  };
4444
4478
  }
4445
4479
  const candidate = raw;
4446
- const enabledRaw = candidate[VALIDATION_ENABLED_FIELD] ?? defaults4.knip.enabled;
4480
+ const enabledRaw = candidate[VALIDATION_ENABLED_FIELD] ?? defaults5.knip.enabled;
4447
4481
  if (typeof enabledRaw !== "boolean") {
4448
4482
  return {
4449
4483
  ok: false,
@@ -4452,7 +4486,7 @@ function validateKnip(raw) {
4452
4486
  }
4453
4487
  return { ok: true, value: { enabled: enabledRaw } };
4454
4488
  }
4455
- function validate8(value) {
4489
+ function validate9(value) {
4456
4490
  if (typeof value !== "object" || value === null || Array.isArray(value)) {
4457
4491
  return { ok: false, error: `${VALIDATION_SECTION} section must be an object` };
4458
4492
  }
@@ -4470,8 +4504,8 @@ function validate8(value) {
4470
4504
  }
4471
4505
  var validationConfigDescriptor = {
4472
4506
  section: VALIDATION_SECTION,
4473
- defaults: defaults4,
4474
- validate: validate8
4507
+ defaults: defaults5,
4508
+ validate: validate9
4475
4509
  };
4476
4510
 
4477
4511
  // src/config/registry.ts
@@ -4482,15 +4516,16 @@ var productionRegistry = [
4482
4516
  fileInclusionConfigDescriptor,
4483
4517
  precommitConfigDescriptor,
4484
4518
  harnessEnvironmentConfigDescriptor,
4485
- diagnoseConfigDescriptor
4519
+ diagnoseConfigDescriptor,
4520
+ runtimeConfigDescriptor
4486
4521
  ];
4487
4522
 
4488
4523
  // src/config/descriptor-digest.ts
4489
4524
  import { createHash as createHash3 } from "crypto";
4490
4525
  var DEFAULT_DESCRIPTOR_PATH = "descriptor section";
4491
- var SHA256_ALGORITHM2 = "sha256";
4526
+ var DESCRIPTOR_DIGEST_SHA256_ALGORITHM = "sha256";
4492
4527
  var UTF8_ENCODING2 = "utf8";
4493
- var HEX_ENCODING2 = "hex";
4528
+ var DESCRIPTOR_DIGEST_HEX_ENCODING = "hex";
4494
4529
  function canonicalDescriptorJson(value, path7 = DEFAULT_DESCRIPTOR_PATH) {
4495
4530
  const normalized = normalizeDescriptorJsonValue(value, path7, /* @__PURE__ */ new WeakSet());
4496
4531
  if (!normalized.ok) return normalized;
@@ -4499,7 +4534,7 @@ function canonicalDescriptorJson(value, path7 = DEFAULT_DESCRIPTOR_PATH) {
4499
4534
  function digestDescriptorSection(value, path7 = DEFAULT_DESCRIPTOR_PATH) {
4500
4535
  const canonical = canonicalDescriptorJson(value, path7);
4501
4536
  if (!canonical.ok) return canonical;
4502
- const sha256 = createHash3(SHA256_ALGORITHM2).update(Buffer.from(canonical.value, UTF8_ENCODING2)).digest(HEX_ENCODING2);
4537
+ const sha256 = createHash3(DESCRIPTOR_DIGEST_SHA256_ALGORITHM).update(Buffer.from(canonical.value, UTF8_ENCODING2)).digest(DESCRIPTOR_DIGEST_HEX_ENCODING);
4503
4538
  return {
4504
4539
  ok: true,
4505
4540
  value: {
@@ -6242,7 +6277,7 @@ import {
6242
6277
  mkdir as nodeMkdir2,
6243
6278
  readFile as nodeReadFile,
6244
6279
  readlink as nodeReadlink,
6245
- rename as nodeRename,
6280
+ rename as nodeRename2,
6246
6281
  rm as nodeRm2,
6247
6282
  symlink as nodeSymlink,
6248
6283
  writeFile as nodeWriteFile
@@ -6254,7 +6289,7 @@ var defaultOccupancyFileSystem = {
6254
6289
  writeFile: async (path7, data) => {
6255
6290
  await nodeWriteFile(path7, data);
6256
6291
  },
6257
- rename: nodeRename,
6292
+ rename: nodeRename2,
6258
6293
  symlink: nodeSymlink,
6259
6294
  readlink: nodeReadlink,
6260
6295
  readFile: nodeReadFile,
@@ -7145,9 +7180,9 @@ var JOURNAL_RUN_STATE_INCOMPLETE_REASON = {
7145
7180
  };
7146
7181
  var JOURNAL_RUN_EVENT = {
7147
7182
  SOURCE: "/spx/journal",
7148
- STARTED_TYPE: "com.outcomeeng.spx.journal.run.started",
7149
- PROGRESS_TYPE: "com.outcomeeng.spx.journal.run.progress",
7150
- COMPLETED_TYPE: "com.outcomeeng.spx.journal.run.completed"
7183
+ STARTED_TYPE: `${RUNTIME_EVENT_NAMESPACE_DEFAULT}.journal.run.started`,
7184
+ PROGRESS_TYPE: `${RUNTIME_EVENT_NAMESPACE_DEFAULT}.journal.run.progress`,
7185
+ COMPLETED_TYPE: `${RUNTIME_EVENT_NAMESPACE_DEFAULT}.journal.run.completed`
7151
7186
  };
7152
7187
  var JOURNAL_RUN_STATE_FIELDS = {
7153
7188
  BRANCH_NAME: "branchName",
@@ -7166,13 +7201,18 @@ var JOURNAL_RUN_STATE_FIELDS = {
7166
7201
  STATUS: "status"
7167
7202
  };
7168
7203
  function foldJournalRunState(events, sealed) {
7169
- if (!sealed) return { ok: false, reason: JOURNAL_RUN_STATE_INCOMPLETE_REASON.UNSEALED };
7204
+ if (!sealed) {
7205
+ return { ok: false, reason: JOURNAL_RUN_STATE_INCOMPLETE_REASON.UNSEALED };
7206
+ }
7170
7207
  let completed;
7171
7208
  for (const event of events) {
7172
7209
  if (event.type === JOURNAL_RUN_EVENT.COMPLETED_TYPE) completed = event;
7173
7210
  }
7174
7211
  if (completed === void 0) {
7175
- return { ok: false, reason: JOURNAL_RUN_STATE_INCOMPLETE_REASON.MISSING_STATE };
7212
+ return {
7213
+ ok: false,
7214
+ reason: JOURNAL_RUN_STATE_INCOMPLETE_REASON.MISSING_STATE
7215
+ };
7176
7216
  }
7177
7217
  const validated = validateJournalRunState(completed.data);
7178
7218
  if (!validated.ok) {
@@ -7185,13 +7225,17 @@ function foldJournalRunState(events, sealed) {
7185
7225
  return validated;
7186
7226
  }
7187
7227
  function isJournalRunStateStatus(value) {
7188
- return typeof value === "string" && Object.values(JOURNAL_RUN_STATE_STATUS).includes(value);
7228
+ return typeof value === "string" && Object.values(JOURNAL_RUN_STATE_STATUS).includes(
7229
+ value
7230
+ );
7189
7231
  }
7190
7232
  function isJournalTargetKind(value) {
7191
7233
  return typeof value === "string" && Object.values(JOURNAL_TARGET_KIND).includes(value);
7192
7234
  }
7193
7235
  function validateJournalRunState(value) {
7194
- if (!isRecord5(value)) return { ok: false, error: "journal run state must be an object" };
7236
+ if (!isRecord5(value)) {
7237
+ return { ok: false, error: "journal run state must be an object" };
7238
+ }
7195
7239
  const identity = readRunStateIdentity(value);
7196
7240
  if (!identity.ok) return identity;
7197
7241
  const body = readRunStateBody(value);
@@ -7203,9 +7247,15 @@ function readRunStateIdentity(value) {
7203
7247
  if (!branchName.ok) return branchName;
7204
7248
  const branchSlug = readString(value, JOURNAL_RUN_STATE_FIELDS.BRANCH_SLUG);
7205
7249
  if (!branchSlug.ok) return branchSlug;
7206
- const targetKind = readTargetKind(value, JOURNAL_RUN_STATE_FIELDS.TARGET_KIND);
7250
+ const targetKind = readTargetKind(
7251
+ value,
7252
+ JOURNAL_RUN_STATE_FIELDS.TARGET_KIND
7253
+ );
7207
7254
  if (!targetKind.ok) return targetKind;
7208
- const pullRequestNumber = readOptionalNonNegativeInteger(value, JOURNAL_RUN_STATE_FIELDS.PULL_REQUEST_NUMBER);
7255
+ const pullRequestNumber = readOptionalNonNegativeInteger(
7256
+ value,
7257
+ JOURNAL_RUN_STATE_FIELDS.PULL_REQUEST_NUMBER
7258
+ );
7209
7259
  if (!pullRequestNumber.ok) return pullRequestNumber;
7210
7260
  const headSha = readString(value, JOURNAL_RUN_STATE_FIELDS.HEAD_SHA);
7211
7261
  if (!headSha.ok) return headSha;
@@ -7227,9 +7277,15 @@ function readRunStateIdentity(value) {
7227
7277
  };
7228
7278
  }
7229
7279
  function readRunStateBody(value) {
7230
- const configDigest = readString(value, JOURNAL_RUN_STATE_FIELDS.CONFIG_DIGEST);
7280
+ const configDigest = readString(
7281
+ value,
7282
+ JOURNAL_RUN_STATE_FIELDS.CONFIG_DIGEST
7283
+ );
7231
7284
  if (!configDigest.ok) return configDigest;
7232
- const participants = readStringArray(value, JOURNAL_RUN_STATE_FIELDS.PARTICIPANTS);
7285
+ const participants = readStringArray(
7286
+ value,
7287
+ JOURNAL_RUN_STATE_FIELDS.PARTICIPANTS
7288
+ );
7233
7289
  if (!participants.ok) return participants;
7234
7290
  const scope2 = readPathFilter(value, JOURNAL_RUN_STATE_FIELDS.SCOPE);
7235
7291
  if (!scope2.ok) return scope2;
@@ -7237,7 +7293,10 @@ function readRunStateBody(value) {
7237
7293
  if (!startedAt.ok) return startedAt;
7238
7294
  const completedAt = readString(value, JOURNAL_RUN_STATE_FIELDS.COMPLETED_AT);
7239
7295
  if (!completedAt.ok) return completedAt;
7240
- const outputPaths = readStringArray(value, JOURNAL_RUN_STATE_FIELDS.OUTPUT_PATHS);
7296
+ const outputPaths = readStringArray(
7297
+ value,
7298
+ JOURNAL_RUN_STATE_FIELDS.OUTPUT_PATHS
7299
+ );
7241
7300
  if (!outputPaths.ok) return outputPaths;
7242
7301
  const status = readStatus(value, JOURNAL_RUN_STATE_FIELDS.STATUS);
7243
7302
  if (!status.ok) return status;
@@ -7266,7 +7325,10 @@ function readOptionalString(value, field) {
7266
7325
  function readOptionalNonNegativeInteger(value, field) {
7267
7326
  const raw = value[field];
7268
7327
  if (raw === void 0) return { ok: true, value: void 0 };
7269
- return typeof raw === "number" && Number.isInteger(raw) && raw >= 0 ? { ok: true, value: raw } : { ok: false, error: `${field} must be a non-negative integer when present` };
7328
+ return typeof raw === "number" && Number.isInteger(raw) && raw >= 0 ? { ok: true, value: raw } : {
7329
+ ok: false,
7330
+ error: `${field} must be a non-negative integer when present`
7331
+ };
7270
7332
  }
7271
7333
  function readStringArray(value, field) {
7272
7334
  const raw = value[field];
@@ -7508,6 +7570,7 @@ function createJournal(backend, identity) {
7508
7570
 
7509
7571
  // src/lib/appendable-journal-store/index.ts
7510
7572
  var SEAL_MARKER_SUFFIX = ".sealed";
7573
+ var APPENDABLE_JOURNAL_SEAL_MARKER_CONTENT = "";
7511
7574
  var LINE_SEPARATOR3 = "\n";
7512
7575
  function appendableJournalSealMarkerPath(runFilePath) {
7513
7576
  return `${runFilePath}${SEAL_MARKER_SUFFIX}`;
@@ -7547,7 +7610,7 @@ function createAppendableJournalStore(options) {
7547
7610
  readAll,
7548
7611
  async seal() {
7549
7612
  await fs8.mkdir(dirname5(sealMarkerPath), { recursive: true });
7550
- await fs8.writeFile(sealMarkerPath, "");
7613
+ await fs8.writeFile(sealMarkerPath, APPENDABLE_JOURNAL_SEAL_MARKER_CONTENT);
7551
7614
  },
7552
7615
  async isSealed() {
7553
7616
  return await readFileOrUndefined(fs8, sealMarkerPath) !== void 0;
@@ -8004,6 +8067,20 @@ async function sealJournalRun(ref, options = {}) {
8004
8067
  return { ok: false, error: `${JOURNAL_RUNTIME_ERROR.SEAL_FAILED}: ${toMessage(error)}` };
8005
8068
  }
8006
8069
  }
8070
+ async function isJournalRunSealed(ref, options = {}) {
8071
+ const runFilePath = bindRunFilePath(ref);
8072
+ if (!runFilePath.ok) return runFilePath;
8073
+ const fs8 = options.fs ?? defaultFileSystem;
8074
+ if (!await runFileExists(fs8, runFilePath.value)) {
8075
+ return { ok: false, error: JOURNAL_RUNTIME_ERROR.RUN_NOT_FOUND };
8076
+ }
8077
+ const store = createAppendableJournalStore({ runFilePath: runFilePath.value, fs: fs8 });
8078
+ try {
8079
+ return { ok: true, value: await store.isSealed() };
8080
+ } catch (error) {
8081
+ return { ok: false, error: `${JOURNAL_RUNTIME_ERROR.READ_FAILED}: ${toMessage(error)}` };
8082
+ }
8083
+ }
8007
8084
  async function renderJournalRun(ref, projection, options = {}) {
8008
8085
  const bound = await bindJournal(ref, options.fs);
8009
8086
  if (!bound.ok) return bound;
@@ -11683,8 +11760,8 @@ var PRODUCT_INPUT_DIGEST_FIELDS = {
11683
11760
  DESCRIPTOR_ID: "descriptorId",
11684
11761
  DIGEST: "digest"
11685
11762
  };
11686
- var SHA256_ALGORITHM3 = "sha256";
11687
- var HEX_ENCODING3 = "hex";
11763
+ var SHA256_ALGORITHM2 = "sha256";
11764
+ var HEX_ENCODING2 = "hex";
11688
11765
  var SEGMENT_SEPARATOR = "-";
11689
11766
  var defaultFileSystem2 = defaultFileSystem;
11690
11767
  function formatTestRunTimestamp(date) {
@@ -11960,7 +12037,7 @@ function isRecord6(value) {
11960
12037
  return typeof value === "object" && value !== null && !Array.isArray(value);
11961
12038
  }
11962
12039
  function sha256Hex2(value) {
11963
- return createHash4(SHA256_ALGORITHM3).update(value).digest(HEX_ENCODING3);
12040
+ return createHash4(SHA256_ALGORITHM2).update(value).digest(HEX_ENCODING2);
11964
12041
  }
11965
12042
  var UNKNOWN_ERROR_MESSAGE = "unknown error";
11966
12043
  function toErrorMessage3(error) {
@@ -19418,13 +19495,13 @@ async function persistVerificationContext(scope2, document, options = {}) {
19418
19495
  const existing = await readExistingContext(fs8, contextPath.value);
19419
19496
  if (!existing.ok) return existing;
19420
19497
  if (existing.value === document.canonicalJson) {
19421
- return { ok: true, value: { digest: document.digest, contextPath: contextPath.value } };
19498
+ return { ok: true, value: { digest: document.digest, contextPath: contextPath.value, created: false } };
19422
19499
  }
19423
19500
  return { ok: false, error: VERIFICATION_CONTEXT_RUNTIME_ERROR.CONTENT_MISMATCH };
19424
19501
  }
19425
19502
  return { ok: false, error: `${VERIFICATION_CONTEXT_RUNTIME_ERROR.WRITE_FAILED}: ${toMessage(error)}` };
19426
19503
  }
19427
- return { ok: true, value: { digest: document.digest, contextPath: contextPath.value } };
19504
+ return { ok: true, value: { digest: document.digest, contextPath: contextPath.value, created: true } };
19428
19505
  }
19429
19506
  async function readExistingContext(fs8, contextPath) {
19430
19507
  try {
@@ -19570,6 +19647,7 @@ var verificationContextDomain = {
19570
19647
  import { readFile as readFile13 } from "fs/promises";
19571
19648
 
19572
19649
  // src/commands/verify/cli.ts
19650
+ import { randomBytes as randomBytes2 } from "crypto";
19573
19651
  import { dirname as dirname12 } from "path";
19574
19652
 
19575
19653
  // src/domains/verify/verify.ts
@@ -19582,18 +19660,35 @@ var VERIFY_VERB = {
19582
19660
  START: "start",
19583
19661
  INPUT: "input",
19584
19662
  APPEND_SCOPE: "append-scope",
19585
- APPEND_FINDING: "append-finding"
19663
+ APPEND_FINDING: "append-finding",
19664
+ FINISH: "finish",
19665
+ STATUS: "status",
19666
+ RENDER: "render"
19667
+ };
19668
+ var VERIFY_LIFECYCLE_ACTION = {
19669
+ SCOPE_ADD: "scope add",
19670
+ FINDING_ADD: "finding add",
19671
+ FINISH: VERIFY_VERB.FINISH
19586
19672
  };
19673
+ var UNSEALED_NEXT_ACTIONS = [
19674
+ VERIFY_LIFECYCLE_ACTION.SCOPE_ADD,
19675
+ VERIFY_LIFECYCLE_ACTION.FINDING_ADD,
19676
+ VERIFY_LIFECYCLE_ACTION.FINISH
19677
+ ];
19587
19678
  var VERIFY_VERIFICATION_TYPE = {
19588
19679
  REVIEW: "review"
19589
19680
  };
19681
+ var VERIFY_VERIFICATION_TYPES = new Set(Object.values(VERIFY_VERIFICATION_TYPE));
19682
+ function isVerifyVerificationType(value) {
19683
+ return VERIFY_VERIFICATION_TYPES.has(value);
19684
+ }
19590
19685
  var REVIEW_FINDING_DISPOSITION = {
19591
19686
  BLOCKING: "BLOCKING",
19592
19687
  DEBT: "DEBT"
19593
19688
  };
19594
19689
  var VERIFY_APPEND_EVENT_TYPE = {
19595
- SCOPE: "io.spx.verify.scope",
19596
- FINDING: "io.spx.verify.finding"
19690
+ SCOPE: `${RUNTIME_EVENT_NAMESPACE_DEFAULT}.verify.scope`,
19691
+ FINDING: `${RUNTIME_EVENT_NAMESPACE_DEFAULT}.verify.finding`
19597
19692
  };
19598
19693
  var VERIFY_EVENT_SOURCE = "/spx/verify";
19599
19694
  var VERIFY_APPEND_EVENT_FIELD = {
@@ -19611,7 +19706,9 @@ var VERIFY_SCOPE_ERROR = {
19611
19706
  var VERIFY_INPUT_DIGEST_PATH = "verify run input";
19612
19707
  function parseChangesetScope(scope2) {
19613
19708
  const separatorIndex = scope2.indexOf(VERIFY_SCOPE_SEPARATOR);
19614
- if (separatorIndex < 0) return { ok: false, error: VERIFY_SCOPE_ERROR.MALFORMED_CHANGESET };
19709
+ if (separatorIndex < 0) {
19710
+ return { ok: false, error: VERIFY_SCOPE_ERROR.MALFORMED_CHANGESET };
19711
+ }
19615
19712
  const base = scope2.slice(0, separatorIndex);
19616
19713
  const head = scope2.slice(separatorIndex + VERIFY_SCOPE_SEPARATOR.length);
19617
19714
  if (base.length === 0 || head.length === 0 || head.includes(VERIFY_SCOPE_SEPARATOR)) {
@@ -19631,7 +19728,10 @@ function buildRunLocator(parts) {
19631
19728
  };
19632
19729
  }
19633
19730
  function digestRunInput(source, content) {
19634
- const digest = digestDescriptorSection({ source, content }, VERIFY_INPUT_DIGEST_PATH);
19731
+ const digest = digestDescriptorSection(
19732
+ { source, content },
19733
+ VERIFY_INPUT_DIGEST_PATH
19734
+ );
19635
19735
  if (!digest.ok) return digest;
19636
19736
  return { ok: true, value: digest.value.sha256 };
19637
19737
  }
@@ -19651,7 +19751,10 @@ function verifyInputRecordPath(scope2) {
19651
19751
  if (!token.ok) return token;
19652
19752
  return {
19653
19753
  ok: true,
19654
- value: join36(runs.value, `${VERIFY_INPUT_RECORD.PREFIX}${token.value}${VERIFY_INPUT_RECORD.SUFFIX}`)
19754
+ value: join36(
19755
+ runs.value,
19756
+ `${VERIFY_INPUT_RECORD.PREFIX}${token.value}${VERIFY_INPUT_RECORD.SUFFIX}`
19757
+ )
19655
19758
  };
19656
19759
  }
19657
19760
  var VERIFY_APPEND_ATTEMPT = 1;
@@ -19700,6 +19803,58 @@ function buildAppendEvent(args) {
19700
19803
  }
19701
19804
  };
19702
19805
  }
19806
+ var VERIFY_TERMINAL_EVENT_TYPE = `${RUNTIME_EVENT_NAMESPACE_DEFAULT}.verify.terminal`;
19807
+ var VERIFY_TERMINAL_EVENT_FIELD = {
19808
+ TERMINAL_STATUS: "terminalStatus"
19809
+ };
19810
+ var VERIFY_TERMINAL_EVENT_ID_PREFIX = "verify-terminal-";
19811
+ function isVerifyTerminalStatus(value) {
19812
+ return isJournalRunStateStatus(value);
19813
+ }
19814
+ function buildTerminalEvent(args) {
19815
+ return {
19816
+ id: `${VERIFY_TERMINAL_EVENT_ID_PREFIX}${args.runToken}`,
19817
+ source: VERIFY_EVENT_SOURCE,
19818
+ type: VERIFY_TERMINAL_EVENT_TYPE,
19819
+ time: args.at.toISOString(),
19820
+ attempt: VERIFY_APPEND_ATTEMPT,
19821
+ data: {
19822
+ [VERIFY_TERMINAL_EVENT_FIELD.TERMINAL_STATUS]: args.terminalStatus
19823
+ }
19824
+ };
19825
+ }
19826
+ var VERIFY_NO_EVENTS_SEQUENCE = 0;
19827
+ function findTerminalEvent(events) {
19828
+ return events.find((event) => event.type === VERIFY_TERMINAL_EVENT_TYPE);
19829
+ }
19830
+ function terminalStatusOf(event) {
19831
+ if (event === void 0 || !isJsonRecord(event.data)) return void 0;
19832
+ const status = event.data[VERIFY_TERMINAL_EVENT_FIELD.TERMINAL_STATUS];
19833
+ return typeof status === "string" ? status : void 0;
19834
+ }
19835
+ function countVerifyFindings(events) {
19836
+ return events.filter(
19837
+ (event) => event.type === VERIFY_APPEND_EVENT_TYPE.FINDING
19838
+ ).length;
19839
+ }
19840
+ function lastSequenceOf(events) {
19841
+ return events.reduce(
19842
+ (max, event) => event.seq > max ? event.seq : max,
19843
+ VERIFY_NO_EVENTS_SEQUENCE
19844
+ );
19845
+ }
19846
+ function projectVerifyRun(events) {
19847
+ const terminal = findTerminalEvent(events);
19848
+ const terminalStatus = terminalStatusOf(terminal);
19849
+ const sealed = terminal !== void 0;
19850
+ return {
19851
+ sealed,
19852
+ ...terminalStatus === void 0 ? {} : { terminalStatus },
19853
+ findingCount: countVerifyFindings(events),
19854
+ lastSequence: lastSequenceOf(events),
19855
+ nextActions: sealed ? [] : UNSEALED_NEXT_ACTIONS
19856
+ };
19857
+ }
19703
19858
 
19704
19859
  // src/commands/verify/cli.ts
19705
19860
  var VERIFY_CLI_EXIT_CODE = {
@@ -19710,19 +19865,40 @@ var VERIFY_CLI_ENV = {
19710
19865
  BRANCH: SPX_VERIFY_ENV.BRANCH
19711
19866
  };
19712
19867
  var VERIFY_CLI_ERROR = {
19713
- INPUT_REQUIRED: "spx verify start requires --input <input-source>",
19714
- RUN_REQUIRED: "spx verify existing-run verbs require an explicit --run <run-token>",
19715
- RUN_NOT_FOUND: "spx verify could not locate the requested run",
19716
- CHANGED_SCOPE_FAILED: "spx verify could not derive the changeset changed-file scope",
19717
- INPUT_PERSIST_FAILED: "spx verify could not persist the recorded run input",
19718
- INPUT_READ_FAILED: "spx verify could not read the recorded run input",
19719
- PAYLOAD_REQUIRED: "spx verify append verbs require --payload <payload-source>",
19720
- IDEMPOTENCY_KEY_REQUIRED: "spx verify append verbs require --idempotency-key <key>",
19721
- PAYLOAD_READ_FAILED: "spx verify could not read the append payload",
19722
- PAYLOAD_INVALID: "spx verify append payload is not valid JSON",
19723
- FINDING_INVALID: "spx verify append-finding payload failed verification-type validation",
19724
- UNSUPPORTED_VERIFICATION_TYPE: "spx verify append-finding has no finding validator for the verification type",
19725
- APPEND_FAILED: "spx verify could not append the evidence event"
19868
+ INPUT_REQUIRED: "spx verification run start requires --input <input-source>",
19869
+ RUN_REQUIRED: "spx verification run existing-run commands require an explicit --run <run-token>",
19870
+ RUN_NOT_FOUND: "spx verification run could not locate the requested run",
19871
+ RUN_SELECTOR_MISMATCH: "spx verification run selector does not match the recorded run",
19872
+ CHANGED_SCOPE_FAILED: "spx verification run could not derive the changeset changed-file scope",
19873
+ INPUT_PERSIST_FAILED: "spx verification run could not persist the recorded run input",
19874
+ INPUT_READ_FAILED: "spx verification run could not read the recorded run input",
19875
+ PAYLOAD_REQUIRED: "spx verification run evidence-add commands require --payload <payload-source>",
19876
+ IDEMPOTENCY_KEY_REQUIRED: "spx verification run evidence-add commands require --idempotency-key <key>",
19877
+ PAYLOAD_READ_FAILED: "spx verification run could not read the evidence payload",
19878
+ PAYLOAD_INVALID: "spx verification run evidence payload is not valid JSON",
19879
+ RUN_FINISHED: "spx verification run cannot add evidence to a finished run",
19880
+ FINDING_INVALID: "spx verification run finding add payload failed verification-type validation",
19881
+ UNSUPPORTED_VERIFICATION_TYPE: "spx verification run verification type is not registered",
19882
+ APPEND_FAILED: "spx verification run could not append the evidence event",
19883
+ TERMINAL_STATUS_REQUIRED: "spx verification run finish requires --terminal-status <status>",
19884
+ TERMINAL_STATUS_INVALID: "spx verification run finish requires a terminal status in the journal terminal-status vocabulary",
19885
+ FINISH_FAILED: "spx verification run could not record terminal completion",
19886
+ SEAL_FAILED: "spx verification run could not seal the run journal",
19887
+ STATUS_FAILED: "spx verification run could not read the run status",
19888
+ RENDER_FAILED: "spx verification run could not render the run projection"
19889
+ };
19890
+ var VERIFY_RUN_NOT_FOUND_DIAGNOSTIC_FIELD = {
19891
+ RUN: "run=",
19892
+ VERIFICATION_TYPE: "verification-type=",
19893
+ SCOPE_TYPE: "scope-type=",
19894
+ SCOPE: "scope=",
19895
+ BACKEND: "backend=",
19896
+ NAMESPACE: "namespace=",
19897
+ TARGET: "target="
19898
+ };
19899
+ var VERIFY_START_ROLLBACK_ARTIFACT = {
19900
+ CONTEXT_FILE: "verification context file",
19901
+ RUN_FILE: "journal run file"
19726
19902
  };
19727
19903
  function okResult3(output) {
19728
19904
  return { exitCode: VERIFY_CLI_EXIT_CODE.OK, output };
@@ -19781,73 +19957,131 @@ async function persistInputRecord(runScope2, record6, deps) {
19781
19957
  const fs8 = deps.fs ?? defaultFileSystem;
19782
19958
  try {
19783
19959
  await fs8.mkdir(dirname12(path7.value), { recursive: true });
19784
- await fs8.writeFile(path7.value, JSON.stringify(record6));
19960
+ await writeFileAtomic(path7.value, JSON.stringify(record6), { fs: fs8, randomBytes: randomBytes2 });
19785
19961
  return { ok: true, value: void 0 };
19786
19962
  } catch (error) {
19787
19963
  return { ok: false, error: `${VERIFY_CLI_ERROR.INPUT_PERSIST_FAILED}: ${toMessage2(error)}` };
19788
19964
  }
19789
19965
  }
19966
+ async function readStartInputContent(source, deps) {
19967
+ try {
19968
+ return { ok: true, value: await deps.readInputSource(source) };
19969
+ } catch (error) {
19970
+ return { ok: false, error: `${VERIFY_CLI_ERROR.INPUT_READ_FAILED}: ${toMessage2(error)}` };
19971
+ }
19972
+ }
19973
+ async function removeStartedRunArtifact(path7, label, deps) {
19974
+ const fs8 = deps.fs ?? defaultFileSystem;
19975
+ try {
19976
+ await fs8.rm(path7, { force: true });
19977
+ return { ok: true, value: void 0 };
19978
+ } catch (error) {
19979
+ return {
19980
+ ok: false,
19981
+ error: `${VERIFY_CLI_ERROR.INPUT_PERSIST_FAILED}: rollback failed for ${label}: ${toMessage2(error)}`
19982
+ };
19983
+ }
19984
+ }
19985
+ async function removeStartedRunArtifacts(artifacts, deps) {
19986
+ const rollbackErrors = [];
19987
+ if (artifacts.contextPath !== void 0) {
19988
+ const contextRollback = await removeStartedRunArtifact(
19989
+ artifacts.contextPath,
19990
+ VERIFY_START_ROLLBACK_ARTIFACT.CONTEXT_FILE,
19991
+ deps
19992
+ );
19993
+ if (!contextRollback.ok) rollbackErrors.push(contextRollback.error);
19994
+ }
19995
+ if (artifacts.runFile !== void 0) {
19996
+ const runRollback = await removeStartedRunArtifact(
19997
+ artifacts.runFile,
19998
+ VERIFY_START_ROLLBACK_ARTIFACT.RUN_FILE,
19999
+ deps
20000
+ );
20001
+ if (!runRollback.ok) rollbackErrors.push(runRollback.error);
20002
+ }
20003
+ if (rollbackErrors.length > 0) return { ok: false, error: rollbackErrors.join("; ") };
20004
+ return { ok: true, value: void 0 };
20005
+ }
20006
+ function isStoredRecordedInput(value) {
20007
+ if (typeof value !== "object" || value === null) return false;
20008
+ const record6 = value;
20009
+ return typeof record6.scopeIdentity === "string" && typeof record6.scopeType === "string" && typeof record6.source === "string" && typeof record6.digest === "string" && typeof record6.content === "string";
20010
+ }
20011
+ function parseRecordedInput(content) {
20012
+ try {
20013
+ const parsed = JSON.parse(content);
20014
+ if (!isStoredRecordedInput(parsed)) {
20015
+ return { ok: false, error: `${VERIFY_CLI_ERROR.INPUT_READ_FAILED}: recorded input is missing required fields` };
20016
+ }
20017
+ return { ok: true, value: parsed };
20018
+ } catch (error) {
20019
+ return { ok: false, error: `${VERIFY_CLI_ERROR.INPUT_READ_FAILED}: ${toMessage2(error)}` };
20020
+ }
20021
+ }
19790
20022
  async function readInputRecordAt(path7, deps) {
19791
20023
  const fs8 = deps.fs ?? defaultFileSystem;
20024
+ let content;
19792
20025
  try {
19793
- const content = await fs8.readFile(path7, STATE_STORE_TEXT_ENCODING);
19794
- return { ok: true, value: JSON.parse(content) };
20026
+ content = await fs8.readFile(path7, STATE_STORE_TEXT_ENCODING);
19795
20027
  } catch (error) {
19796
20028
  if (hasErrorCode(error, ERROR_CODE_NOT_FOUND)) return { ok: true, value: void 0 };
19797
20029
  return { ok: false, error: `${VERIFY_CLI_ERROR.INPUT_READ_FAILED}: ${toMessage2(error)}` };
19798
20030
  }
20031
+ const parsed = parseRecordedInput(content);
20032
+ if (!parsed.ok) return parsed;
20033
+ return { ok: true, value: parsed.value };
19799
20034
  }
19800
- function verifyRunNotFoundDiagnostic(context) {
20035
+ function verifyRunLocatorDiagnostic(summary, context) {
19801
20036
  return [
19802
- VERIFY_CLI_ERROR.RUN_NOT_FOUND,
19803
- `run=${context.runToken}`,
19804
- `verification-type=${context.verificationType}`,
19805
- `scope-type=${context.scopeType}`,
19806
- `scope=${context.scopeIdentity}`,
19807
- `backend=${context.backendIdentity}`,
19808
- `namespace=${context.storageNamespace}`,
19809
- `target=${context.searchedTarget}`
20037
+ summary,
20038
+ `${VERIFY_RUN_NOT_FOUND_DIAGNOSTIC_FIELD.RUN}${context.runToken}`,
20039
+ `${VERIFY_RUN_NOT_FOUND_DIAGNOSTIC_FIELD.VERIFICATION_TYPE}${context.verificationType}`,
20040
+ `${VERIFY_RUN_NOT_FOUND_DIAGNOSTIC_FIELD.SCOPE_TYPE}${context.scopeType}`,
20041
+ `${VERIFY_RUN_NOT_FOUND_DIAGNOSTIC_FIELD.SCOPE}${context.scopeIdentity}`,
20042
+ `${VERIFY_RUN_NOT_FOUND_DIAGNOSTIC_FIELD.BACKEND}${context.backendIdentity}`,
20043
+ `${VERIFY_RUN_NOT_FOUND_DIAGNOSTIC_FIELD.NAMESPACE}${context.storageNamespace}`,
20044
+ `${VERIFY_RUN_NOT_FOUND_DIAGNOSTIC_FIELD.TARGET}${context.searchedTarget}`
19810
20045
  ].join(" ");
19811
20046
  }
19812
- async function verifyStartCommand(options, deps) {
19813
- if (options.scopeType !== VERIFY_SCOPE_TYPE.CHANGESET) return errorResult3(VERIFY_SCOPE_ERROR.UNSUPPORTED_SCOPE_TYPE);
19814
- if (options.input.trim().length === 0) return errorResult3(VERIFY_CLI_ERROR.INPUT_REQUIRED);
19815
- const scope2 = parseChangesetScope(options.scope);
19816
- if (!scope2.ok) return errorResult3(scope2.error);
19817
- const resolved = await resolveVerifyScope(deps);
19818
- if (!resolved.ok) return errorResult3(resolved.error);
19819
- const inputContent = await deps.readInputSource(options.input);
19820
- const inputDigest = digestRunInput(options.input, inputContent);
19821
- if (!inputDigest.ok) return errorResult3(inputDigest.error);
19822
- const context = await verificationContextCreateCommand(
19823
- {
19824
- subject: VERIFICATION_CONTEXT_SUBJECT_KIND.CHANGESET,
19825
- base: scope2.value.base,
19826
- head: scope2.value.head,
19827
- predicate: options.verificationType,
19828
- workflow: options.verificationType
19829
- },
19830
- forwardDeps(deps)
19831
- );
19832
- if (context.exitCode !== VERIFY_CLI_EXIT_CODE.OK) return errorResult3(context.output);
19833
- const contextDigest = JSON.parse(context.output).digest;
20047
+ function verifyRunNotFoundDiagnostic(context) {
20048
+ return verifyRunLocatorDiagnostic(VERIFY_CLI_ERROR.RUN_NOT_FOUND, context);
20049
+ }
20050
+ async function completeVerifyStartCommand(args) {
20051
+ const { options, deps } = args;
19834
20052
  const opened = await journalOpenCommand(
19835
- { type: options.verificationType, branchSlug: resolved.value.branchSlug },
20053
+ { type: options.verificationType, branchSlug: args.branchSlug },
19836
20054
  forwardDeps(deps)
19837
20055
  );
19838
- if (opened.exitCode !== VERIFY_CLI_EXIT_CODE.OK) return errorResult3(opened.output);
20056
+ if (opened.exitCode !== VERIFY_CLI_EXIT_CODE.OK) {
20057
+ const rollback = await removeStartedRunArtifacts(
20058
+ { ...args.contextCreated ? { contextPath: args.contextPath } : {} },
20059
+ deps
20060
+ );
20061
+ return errorResult3(rollback.ok ? opened.output : `${opened.output}; ${rollback.error}`);
20062
+ }
19839
20063
  const { runToken, runFile } = JSON.parse(opened.output);
19840
20064
  const runScope2 = {
19841
- productDir: resolved.value.productDir,
19842
- branchSlug: resolved.value.branchSlug,
20065
+ productDir: args.productDir,
20066
+ branchSlug: args.branchSlug,
19843
20067
  type: options.verificationType,
19844
20068
  runToken
19845
20069
  };
19846
- const recorded = { source: options.input, digest: inputDigest.value, content: inputContent };
20070
+ const recorded = {
20071
+ scopeIdentity: options.scope,
20072
+ scopeType: options.scopeType,
20073
+ source: options.input,
20074
+ digest: args.inputDigest,
20075
+ content: args.inputContent
20076
+ };
19847
20077
  const persisted = await persistInputRecord(runScope2, recorded, deps);
19848
- if (!persisted.ok) return errorResult3(persisted.error);
19849
- const changedScope = await resolveChangedScope(scope2.value, deps);
19850
- if (!changedScope.ok) return errorResult3(changedScope.error);
20078
+ if (!persisted.ok) {
20079
+ const rollback = await removeStartedRunArtifacts(
20080
+ { ...args.contextCreated ? { contextPath: args.contextPath } : {}, runFile },
20081
+ deps
20082
+ );
20083
+ return errorResult3(rollback.ok ? persisted.error : `${persisted.error}; ${rollback.error}`);
20084
+ }
19851
20085
  const namespace = verifyRunsDir(runScope2);
19852
20086
  if (!namespace.ok) return errorResult3(namespace.error);
19853
20087
  const locator = buildRunLocator({
@@ -19855,52 +20089,69 @@ async function verifyStartCommand(options, deps) {
19855
20089
  verificationType: options.verificationType,
19856
20090
  scopeType: options.scopeType,
19857
20091
  scopeIdentity: options.scope,
19858
- backendIdentity: resolved.value.backendIdentity,
20092
+ backendIdentity: args.backendIdentity,
19859
20093
  storageNamespace: namespace.value,
19860
20094
  runTarget: runFile
19861
20095
  });
19862
20096
  const report2 = {
19863
20097
  runToken,
19864
- contextDigest,
19865
- changedScope: changedScope.value,
19866
- input: { source: options.input, digest: inputDigest.value },
20098
+ contextDigest: args.contextDigest,
20099
+ changedScope: args.changedScope,
20100
+ input: { source: options.input, digest: args.inputDigest },
19867
20101
  locator
19868
20102
  };
19869
20103
  return okResult3(JSON.stringify(report2));
19870
20104
  }
19871
- async function verifyInputCommand(options, deps) {
19872
- if (options.run.trim().length === 0) return errorResult3(VERIFY_CLI_ERROR.RUN_REQUIRED);
20105
+ async function verifyStartCommand(options, deps) {
20106
+ if (!isVerifyVerificationType(options.verificationType)) {
20107
+ return errorResult3(VERIFY_CLI_ERROR.UNSUPPORTED_VERIFICATION_TYPE);
20108
+ }
20109
+ if (options.scopeType !== VERIFY_SCOPE_TYPE.CHANGESET) return errorResult3(VERIFY_SCOPE_ERROR.UNSUPPORTED_SCOPE_TYPE);
20110
+ if (options.input.trim().length === 0) return errorResult3(VERIFY_CLI_ERROR.INPUT_REQUIRED);
20111
+ const scope2 = parseChangesetScope(options.scope);
20112
+ if (!scope2.ok) return errorResult3(scope2.error);
19873
20113
  const resolved = await resolveVerifyScope(deps);
19874
20114
  if (!resolved.ok) return errorResult3(resolved.error);
19875
- const runScope2 = {
20115
+ const inputContent = await readStartInputContent(options.input, deps);
20116
+ if (!inputContent.ok) return errorResult3(inputContent.error);
20117
+ const inputDigest = digestRunInput(options.input, inputContent.value);
20118
+ if (!inputDigest.ok) return errorResult3(inputDigest.error);
20119
+ const changedScope = await resolveChangedScope(scope2.value, deps);
20120
+ if (!changedScope.ok) return errorResult3(changedScope.error);
20121
+ const context = await verificationContextCreateCommand(
20122
+ {
20123
+ subject: VERIFICATION_CONTEXT_SUBJECT_KIND.CHANGESET,
20124
+ base: scope2.value.base,
20125
+ head: scope2.value.head,
20126
+ predicate: options.verificationType,
20127
+ workflow: options.verificationType
20128
+ },
20129
+ forwardDeps(deps)
20130
+ );
20131
+ if (context.exitCode !== VERIFY_CLI_EXIT_CODE.OK) return errorResult3(context.output);
20132
+ const { digest: contextDigest, contextPath, created: contextCreated } = JSON.parse(context.output);
20133
+ return completeVerifyStartCommand({
20134
+ options,
20135
+ deps,
19876
20136
  productDir: resolved.value.productDir,
19877
20137
  branchSlug: resolved.value.branchSlug,
19878
- type: options.verificationType,
19879
- runToken: options.run
19880
- };
19881
- const path7 = verifyInputRecordPath(runScope2);
19882
- if (!path7.ok) return errorResult3(path7.error);
19883
- const namespace = verifyRunsDir(runScope2);
19884
- if (!namespace.ok) return errorResult3(namespace.error);
19885
- const record6 = await readInputRecordAt(path7.value, deps);
19886
- if (!record6.ok) return errorResult3(record6.error);
19887
- if (record6.value === void 0) {
19888
- return errorResult3(
19889
- verifyRunNotFoundDiagnostic({
19890
- runToken: options.run,
19891
- verificationType: options.verificationType,
19892
- scopeType: options.scopeType,
19893
- scopeIdentity: options.scope,
19894
- backendIdentity: resolved.value.backendIdentity,
19895
- storageNamespace: namespace.value,
19896
- searchedTarget: path7.value
19897
- })
19898
- );
19899
- }
20138
+ backendIdentity: resolved.value.backendIdentity,
20139
+ changedScope: changedScope.value,
20140
+ inputDigest: inputDigest.value,
20141
+ inputContent: inputContent.value,
20142
+ contextDigest,
20143
+ contextPath,
20144
+ contextCreated
20145
+ });
20146
+ }
20147
+ async function verifyInputCommand(options, deps) {
20148
+ const run = await resolveExistingRun(options, deps);
20149
+ if (!run.ok) return errorResult3(run.error);
20150
+ const record6 = run.value.recordedInput;
19900
20151
  const report2 = {
19901
- source: record6.value.source,
19902
- digest: record6.value.digest,
19903
- content: record6.value.content
20152
+ source: record6.source,
20153
+ digest: record6.digest,
20154
+ content: record6.content
19904
20155
  };
19905
20156
  return okResult3(JSON.stringify(report2));
19906
20157
  }
@@ -19909,12 +20160,36 @@ async function readRunJournalEvents(scope2, deps) {
19909
20160
  if (read.exitCode !== VERIFY_CLI_EXIT_CODE.OK) return { ok: false, error: read.output };
19910
20161
  return { ok: true, value: JSON.parse(read.output) };
19911
20162
  }
20163
+ async function readAppendExistingEvents(options, deps, journalScope, backendIdentity, namespace) {
20164
+ const existingEvents = await readRunJournalEvents(journalScope, deps);
20165
+ if (!existingEvents.ok) {
20166
+ if (existingEvents.error === JOURNAL_RUNTIME_ERROR.RUN_NOT_FOUND) {
20167
+ return {
20168
+ ok: false,
20169
+ error: appendRunNotFoundDiagnostic(options, backendIdentity, namespace)
20170
+ };
20171
+ }
20172
+ return { ok: false, error: `${VERIFY_CLI_ERROR.APPEND_FAILED}: ${existingEvents.error}` };
20173
+ }
20174
+ if (findTerminalEvent(existingEvents.value) !== void 0) {
20175
+ return { ok: false, error: VERIFY_CLI_ERROR.RUN_FINISHED };
20176
+ }
20177
+ return existingEvents;
20178
+ }
19912
20179
  async function prepareAppend(options, deps) {
19913
20180
  if (options.run.trim().length === 0) return { ok: false, error: VERIFY_CLI_ERROR.RUN_REQUIRED };
20181
+ if (!isVerifyVerificationType(options.verificationType)) {
20182
+ return { ok: false, error: VERIFY_CLI_ERROR.UNSUPPORTED_VERIFICATION_TYPE };
20183
+ }
19914
20184
  if (options.payload.trim().length === 0) return { ok: false, error: VERIFY_CLI_ERROR.PAYLOAD_REQUIRED };
19915
20185
  if (options.idempotencyKey.trim().length === 0) {
19916
20186
  return { ok: false, error: VERIFY_CLI_ERROR.IDEMPOTENCY_KEY_REQUIRED };
19917
20187
  }
20188
+ if (options.scopeType !== VERIFY_SCOPE_TYPE.CHANGESET) {
20189
+ return { ok: false, error: VERIFY_SCOPE_ERROR.UNSUPPORTED_SCOPE_TYPE };
20190
+ }
20191
+ const scope2 = parseChangesetScope(options.scope);
20192
+ if (!scope2.ok) return scope2;
19918
20193
  const readPayload = deps.readPayloadSource;
19919
20194
  const binding = deps.journalBinding;
19920
20195
  if (readPayload === void 0 || binding === void 0) return { ok: false, error: VERIFY_CLI_ERROR.APPEND_FAILED };
@@ -19928,25 +20203,53 @@ async function prepareAppend(options, deps) {
19928
20203
  };
19929
20204
  const namespace = verifyRunsDir(runScope2);
19930
20205
  if (!namespace.ok) return namespace;
20206
+ const journalScope = {
20207
+ type: options.verificationType,
20208
+ runToken: options.run,
20209
+ branchSlug: resolved.value.branchSlug
20210
+ };
20211
+ const existingEvents = await readAppendExistingEvents(
20212
+ options,
20213
+ deps,
20214
+ journalScope,
20215
+ resolved.value.backendIdentity,
20216
+ namespace.value
20217
+ );
20218
+ if (!existingEvents.ok) return existingEvents;
19931
20219
  const inputPath = verifyInputRecordPath(runScope2);
19932
20220
  if (!inputPath.ok) return inputPath;
19933
20221
  const inputRecord = await readInputRecordAt(inputPath.value, deps);
19934
20222
  if (!inputRecord.ok) return inputRecord;
19935
20223
  if (inputRecord.value === void 0) {
19936
- return { ok: false, error: appendRunNotFoundDiagnostic(options, resolved.value.backendIdentity, namespace.value) };
20224
+ return {
20225
+ ok: false,
20226
+ error: appendRunNotFoundDiagnostic(options, resolved.value.backendIdentity, namespace.value, inputPath.value)
20227
+ };
20228
+ }
20229
+ if (!recordedSelectorMatches(inputRecord.value, options)) {
20230
+ return {
20231
+ ok: false,
20232
+ error: appendRunSelectorMismatchDiagnostic(
20233
+ options,
20234
+ resolved.value.backendIdentity,
20235
+ namespace.value,
20236
+ inputPath.value
20237
+ )
20238
+ };
19937
20239
  }
19938
20240
  return {
19939
20241
  ok: true,
19940
20242
  value: {
19941
20243
  readPayload,
19942
20244
  binding,
19943
- journalScope: { type: options.verificationType, runToken: options.run, branchSlug: resolved.value.branchSlug },
20245
+ journalScope,
19944
20246
  namespace: namespace.value,
19945
- backendIdentity: resolved.value.backendIdentity
20247
+ backendIdentity: resolved.value.backendIdentity,
20248
+ existingEvents: existingEvents.value
19946
20249
  }
19947
20250
  };
19948
20251
  }
19949
- function appendRunNotFoundDiagnostic(options, backendIdentity, namespace) {
20252
+ function appendRunNotFoundDiagnostic(options, backendIdentity, namespace, searchedTarget = namespace) {
19950
20253
  return verifyRunNotFoundDiagnostic({
19951
20254
  runToken: options.run,
19952
20255
  verificationType: options.verificationType,
@@ -19954,7 +20257,18 @@ function appendRunNotFoundDiagnostic(options, backendIdentity, namespace) {
19954
20257
  scopeIdentity: options.scope,
19955
20258
  backendIdentity,
19956
20259
  storageNamespace: namespace,
19957
- searchedTarget: namespace
20260
+ searchedTarget
20261
+ });
20262
+ }
20263
+ function appendRunSelectorMismatchDiagnostic(options, backendIdentity, namespace, searchedTarget) {
20264
+ return verifyRunLocatorDiagnostic(VERIFY_CLI_ERROR.RUN_SELECTOR_MISMATCH, {
20265
+ runToken: options.run,
20266
+ verificationType: options.verificationType,
20267
+ scopeType: options.scopeType,
20268
+ scopeIdentity: options.scope,
20269
+ backendIdentity,
20270
+ storageNamespace: namespace,
20271
+ searchedTarget
19958
20272
  });
19959
20273
  }
19960
20274
  function validateAppendFinding(verb, verificationType, payload) {
@@ -19970,16 +20284,9 @@ function appendEventType(verb) {
19970
20284
  async function verifyAppend(options, deps, verb) {
19971
20285
  const prepared = await prepareAppend(options, deps);
19972
20286
  if (!prepared.ok) return errorResult3(prepared.error);
19973
- const { readPayload, binding, journalScope, namespace, backendIdentity } = prepared.value;
20287
+ const { readPayload, binding, journalScope, existingEvents } = prepared.value;
19974
20288
  const eventType = appendEventType(verb);
19975
- const before = await readRunJournalEvents(journalScope, deps);
19976
- if (!before.ok) {
19977
- if (before.error === JOURNAL_RUNTIME_ERROR.RUN_NOT_FOUND) {
19978
- return errorResult3(appendRunNotFoundDiagnostic(options, backendIdentity, namespace));
19979
- }
19980
- return errorResult3(`${VERIFY_CLI_ERROR.APPEND_FAILED}: ${before.error}`);
19981
- }
19982
- const existing = findAppendedSequence(before.value, options.idempotencyKey, eventType);
20289
+ const existing = findAppendedSequence(existingEvents, options.idempotencyKey, eventType);
19983
20290
  if (existing !== void 0) {
19984
20291
  const report3 = { sequence: existing, idempotent: true };
19985
20292
  return okResult3(JSON.stringify(report3));
@@ -20017,24 +20324,270 @@ async function verifyAppendScopeCommand(options, deps) {
20017
20324
  async function verifyAppendFindingCommand(options, deps) {
20018
20325
  return verifyAppend(options, deps, VERIFY_VERB.APPEND_FINDING);
20019
20326
  }
20327
+ function existingRunNotFound(run, options) {
20328
+ return verifyRunNotFoundDiagnostic({
20329
+ runToken: run.runToken,
20330
+ verificationType: options.verificationType,
20331
+ scopeType: options.scopeType,
20332
+ scopeIdentity: options.scope,
20333
+ backendIdentity: run.backendIdentity,
20334
+ storageNamespace: run.namespace,
20335
+ searchedTarget: run.inputRecordPath
20336
+ });
20337
+ }
20338
+ function existingRunSelectorMismatch(run, options) {
20339
+ return verifyRunLocatorDiagnostic(VERIFY_CLI_ERROR.RUN_SELECTOR_MISMATCH, {
20340
+ runToken: run.runToken,
20341
+ verificationType: options.verificationType,
20342
+ scopeType: options.scopeType,
20343
+ scopeIdentity: options.scope,
20344
+ backendIdentity: run.backendIdentity,
20345
+ storageNamespace: run.namespace,
20346
+ searchedTarget: run.inputRecordPath
20347
+ });
20348
+ }
20349
+ function recordedSelectorMatches(record6, options) {
20350
+ return record6.scopeType === options.scopeType && record6.scopeIdentity === options.scope;
20351
+ }
20352
+ async function readExistingRecordedInput(run, deps) {
20353
+ return readInputRecordAt(run.inputRecordPath, deps);
20354
+ }
20355
+ async function resolveExistingRunAddress(options, deps) {
20356
+ if (!isVerifyVerificationType(options.verificationType)) {
20357
+ return { ok: false, error: VERIFY_CLI_ERROR.UNSUPPORTED_VERIFICATION_TYPE };
20358
+ }
20359
+ if (options.scopeType !== VERIFY_SCOPE_TYPE.CHANGESET) {
20360
+ return { ok: false, error: VERIFY_SCOPE_ERROR.UNSUPPORTED_SCOPE_TYPE };
20361
+ }
20362
+ const scope2 = parseChangesetScope(options.scope);
20363
+ if (!scope2.ok) return scope2;
20364
+ if (options.run.trim().length === 0) return { ok: false, error: VERIFY_CLI_ERROR.RUN_REQUIRED };
20365
+ const resolved = await resolveVerifyScope(deps);
20366
+ if (!resolved.ok) return resolved;
20367
+ const runScope2 = {
20368
+ productDir: resolved.value.productDir,
20369
+ branchSlug: resolved.value.branchSlug,
20370
+ type: options.verificationType,
20371
+ runToken: options.run
20372
+ };
20373
+ const namespace = verifyRunsDir(runScope2);
20374
+ if (!namespace.ok) return namespace;
20375
+ const inputPath = verifyInputRecordPath(runScope2);
20376
+ if (!inputPath.ok) return inputPath;
20377
+ return {
20378
+ ok: true,
20379
+ value: {
20380
+ productDir: resolved.value.productDir,
20381
+ runToken: options.run,
20382
+ journalScope: { type: options.verificationType, runToken: options.run, branchSlug: resolved.value.branchSlug },
20383
+ namespace: namespace.value,
20384
+ backendIdentity: resolved.value.backendIdentity,
20385
+ inputRecordPath: inputPath.value
20386
+ }
20387
+ };
20388
+ }
20389
+ async function resolveExistingRun(options, deps) {
20390
+ const address = await resolveExistingRunAddress(options, deps);
20391
+ if (!address.ok) return address;
20392
+ const inputRecord = await readExistingRecordedInput(address.value, deps);
20393
+ if (!inputRecord.ok) return inputRecord;
20394
+ if (inputRecord.value === void 0) {
20395
+ return { ok: false, error: existingRunNotFound(address.value, options) };
20396
+ }
20397
+ if (!recordedSelectorMatches(inputRecord.value, options)) {
20398
+ return { ok: false, error: existingRunSelectorMismatch(address.value, options) };
20399
+ }
20400
+ const run = { ...address.value, recordedInput: inputRecord.value };
20401
+ return { ok: true, value: run };
20402
+ }
20403
+ function isRecordedInputReadFailure(error) {
20404
+ return error.startsWith(VERIFY_CLI_ERROR.INPUT_READ_FAILED);
20405
+ }
20406
+ async function readRecordedInputForProjection(run, options, events, deps) {
20407
+ const terminal = findTerminalEvent(events);
20408
+ const inputRecord = await readExistingRecordedInput(run, deps);
20409
+ if (!inputRecord.ok) {
20410
+ if (terminal !== void 0 && isRecordedInputReadFailure(inputRecord.error)) {
20411
+ return { ok: true, value: void 0 };
20412
+ }
20413
+ return inputRecord;
20414
+ }
20415
+ if (inputRecord.value === void 0) return { ok: true, value: void 0 };
20416
+ if (!recordedSelectorMatches(inputRecord.value, options)) {
20417
+ return { ok: false, error: existingRunSelectorMismatch(run, options) };
20418
+ }
20419
+ return { ok: true, value: inputRecord.value };
20420
+ }
20421
+ function verifyFinishReport(runToken, projection) {
20422
+ return {
20423
+ runToken,
20424
+ ...projection.terminalStatus === void 0 ? {} : { terminalStatus: projection.terminalStatus },
20425
+ sealed: projection.sealed,
20426
+ findingCount: projection.findingCount,
20427
+ lastSequence: projection.lastSequence
20428
+ };
20429
+ }
20430
+ async function isJournalPhysicallySealed(run, deps) {
20431
+ return isJournalRunSealed(
20432
+ { ...run.journalScope, productDir: run.productDir },
20433
+ { ...deps.fs === void 0 ? {} : { fs: deps.fs } }
20434
+ );
20435
+ }
20436
+ async function sealExistingRun(run, deps) {
20437
+ const sealed = await journalSealCommand(run.journalScope, forwardDeps(deps));
20438
+ if (sealed.exitCode !== VERIFY_CLI_EXIT_CODE.OK) {
20439
+ return errorResult3(`${VERIFY_CLI_ERROR.SEAL_FAILED}: ${sealed.output}`);
20440
+ }
20441
+ return void 0;
20442
+ }
20443
+ function finishReadFailure(run, options, error) {
20444
+ if (error === JOURNAL_RUNTIME_ERROR.RUN_NOT_FOUND) {
20445
+ return errorResult3(existingRunNotFound(run, options));
20446
+ }
20447
+ return errorResult3(`${VERIFY_CLI_ERROR.FINISH_FAILED}: ${error}`);
20448
+ }
20449
+ async function finishProjectionResult(run, deps) {
20450
+ const events = await readRunJournalEvents(run.journalScope, deps);
20451
+ if (!events.ok) return errorResult3(`${VERIFY_CLI_ERROR.FINISH_FAILED}: ${events.error}`);
20452
+ return okResult3(JSON.stringify(verifyFinishReport(run.runToken, projectVerifyRun(events.value))));
20453
+ }
20454
+ async function retrySealForTerminalRun(run, deps) {
20455
+ const physicallySealed = await isJournalPhysicallySealed(run, deps);
20456
+ if (!physicallySealed.ok) return void 0;
20457
+ if (physicallySealed.value) return void 0;
20458
+ return sealExistingRun(run, deps);
20459
+ }
20460
+ async function verifyFinishCommand(options, deps) {
20461
+ if (options.terminalStatus.trim().length === 0) return errorResult3(VERIFY_CLI_ERROR.TERMINAL_STATUS_REQUIRED);
20462
+ if (!isVerifyTerminalStatus(options.terminalStatus)) return errorResult3(VERIFY_CLI_ERROR.TERMINAL_STATUS_INVALID);
20463
+ const run = await resolveExistingRunAddress(options, deps);
20464
+ if (!run.ok) return errorResult3(run.error);
20465
+ const before = await readRunJournalEvents(run.value.journalScope, deps);
20466
+ if (!before.ok) {
20467
+ return finishReadFailure(run.value, options, before.error);
20468
+ }
20469
+ const inputRecord = await readRecordedInputForProjection(run.value, options, before.value, deps);
20470
+ if (!inputRecord.ok) return errorResult3(inputRecord.error);
20471
+ if (findTerminalEvent(before.value) !== void 0) {
20472
+ const retrySeal = await retrySealForTerminalRun(run.value, deps);
20473
+ return retrySeal ?? finishProjectionResult(run.value, deps);
20474
+ }
20475
+ if (inputRecord.value === void 0) {
20476
+ return errorResult3(existingRunNotFound(run.value, options));
20477
+ }
20478
+ const binding = deps.journalBinding;
20479
+ if (binding === void 0) return errorResult3(VERIFY_CLI_ERROR.FINISH_FAILED);
20480
+ const event = buildTerminalEvent({
20481
+ runToken: run.value.runToken,
20482
+ terminalStatus: options.terminalStatus,
20483
+ at: deps.now?.() ?? /* @__PURE__ */ new Date()
20484
+ });
20485
+ const appended = await journalAppendCommand(run.value.journalScope, event, binding, forwardDeps(deps));
20486
+ if (appended.exitCode !== VERIFY_CLI_EXIT_CODE.OK) {
20487
+ return errorResult3(`${VERIFY_CLI_ERROR.FINISH_FAILED}: ${appended.output}`);
20488
+ }
20489
+ const sealResult = await sealExistingRun(run.value, deps);
20490
+ if (sealResult !== void 0) return sealResult;
20491
+ return finishProjectionResult(run.value, deps);
20492
+ }
20493
+ async function verifyStatusCommand(options, deps) {
20494
+ const run = await resolveExistingRunAddress(options, deps);
20495
+ if (!run.ok) return errorResult3(run.error);
20496
+ const events = await readRunJournalEvents(run.value.journalScope, deps);
20497
+ if (!events.ok) {
20498
+ if (events.error === JOURNAL_RUNTIME_ERROR.RUN_NOT_FOUND) {
20499
+ return errorResult3(existingRunNotFound(run.value, options));
20500
+ }
20501
+ return errorResult3(`${VERIFY_CLI_ERROR.STATUS_FAILED}: ${events.error}`);
20502
+ }
20503
+ const inputRecord = await readRecordedInputForProjection(run.value, options, events.value, deps);
20504
+ if (!inputRecord.ok) return errorResult3(inputRecord.error);
20505
+ if (inputRecord.value === void 0 && findTerminalEvent(events.value) === void 0) {
20506
+ return errorResult3(existingRunNotFound(run.value, options));
20507
+ }
20508
+ const projection = projectVerifyRun(events.value);
20509
+ const report2 = {
20510
+ runToken: run.value.runToken,
20511
+ verificationType: options.verificationType,
20512
+ scopeType: options.scopeType,
20513
+ sealed: projection.sealed,
20514
+ lastSequence: projection.lastSequence,
20515
+ ...projection.terminalStatus === void 0 ? {} : { terminalStatus: projection.terminalStatus },
20516
+ findingCount: projection.findingCount,
20517
+ nextActions: projection.nextActions
20518
+ };
20519
+ return okResult3(JSON.stringify(report2));
20520
+ }
20521
+ async function verifyRenderCommand(options, deps) {
20522
+ const run = await resolveExistingRunAddress(options, deps);
20523
+ if (!run.ok) return errorResult3(run.error);
20524
+ const events = await readRunJournalEvents(run.value.journalScope, deps);
20525
+ if (!events.ok) {
20526
+ if (events.error === JOURNAL_RUNTIME_ERROR.RUN_NOT_FOUND) {
20527
+ return errorResult3(existingRunNotFound(run.value, options));
20528
+ }
20529
+ return errorResult3(`${VERIFY_CLI_ERROR.RENDER_FAILED}: ${events.error}`);
20530
+ }
20531
+ const inputRecord = await readRecordedInputForProjection(run.value, options, events.value, deps);
20532
+ if (!inputRecord.ok) return errorResult3(inputRecord.error);
20533
+ if (inputRecord.value === void 0 && findTerminalEvent(events.value) === void 0) {
20534
+ return errorResult3(existingRunNotFound(run.value, options));
20535
+ }
20536
+ const projection = projectVerifyRun(events.value);
20537
+ const report2 = {
20538
+ runToken: run.value.runToken,
20539
+ findingCount: projection.findingCount,
20540
+ sealed: projection.sealed,
20541
+ ...projection.terminalStatus === void 0 ? {} : { terminalStatus: projection.terminalStatus },
20542
+ events: events.value
20543
+ };
20544
+ return okResult3(JSON.stringify(report2));
20545
+ }
20020
20546
 
20021
20547
  // src/interfaces/cli/verify.ts
20548
+ var VERIFICATION_RUN_CLI_SURFACE = {
20549
+ addCommandName: "add",
20550
+ findingResourceCommandName: "finding",
20551
+ forbiddenRootCommandName: "verify",
20552
+ forbiddenRunHelpTerms: ["Append", "append"],
20553
+ forbiddenRunCommandNames: ["journal", "event", "append-scope", "append-finding"],
20554
+ rootCommandName: "verification",
20555
+ runCommandName: "run",
20556
+ scopeResourceCommandName: "scope"
20557
+ };
20022
20558
  var VERIFY_CLI = {
20023
- commandName: "verify",
20559
+ addCommandName: VERIFICATION_RUN_CLI_SURFACE.addCommandName,
20560
+ commandName: VERIFICATION_RUN_CLI_SURFACE.rootCommandName,
20024
20561
  description: "Record and replay a typed verification run",
20025
20562
  startCommandName: VERIFY_VERB.START,
20026
20563
  inputCommandName: VERIFY_VERB.INPUT,
20027
- appendScopeCommandName: VERIFY_VERB.APPEND_SCOPE,
20028
- appendFindingCommandName: VERIFY_VERB.APPEND_FINDING,
20564
+ findingCommandName: VERIFICATION_RUN_CLI_SURFACE.findingResourceCommandName,
20565
+ runCommandName: VERIFICATION_RUN_CLI_SURFACE.runCommandName,
20566
+ scopeCommandName: VERIFICATION_RUN_CLI_SURFACE.scopeResourceCommandName,
20567
+ finishCommandName: VERIFY_VERB.FINISH,
20568
+ statusCommandName: VERIFY_VERB.STATUS,
20569
+ renderCommandName: VERIFY_VERB.RENDER,
20029
20570
  verificationTypeOption: "--verification-type <type>",
20030
20571
  scopeTypeOption: "--scope-type <scope-type>",
20031
20572
  scopeOption: "--scope <base>..<head>",
20032
20573
  inputOption: "--input <input-source>",
20033
20574
  runOption: "--run <token>",
20034
20575
  payloadOption: "--payload <payload-source>",
20035
- idempotencyKeyOption: "--idempotency-key <key>"
20576
+ payloadOptionDescription: "Evidence payload source; stdin or a file path",
20577
+ idempotencyKeyOption: "--idempotency-key <key>",
20578
+ idempotencyKeyOptionDescription: "Caller-supplied idempotency key for the evidence add",
20579
+ terminalStatusOption: "--terminal-status <status>"
20036
20580
  };
20037
20581
  var CLI_SOURCE_ENCODING = "utf8";
20582
+ var DEFAULT_VERIFY_CLI_HANDLERS = {
20583
+ appendFinding: verifyAppendFindingCommand,
20584
+ appendScope: verifyAppendScopeCommand,
20585
+ finish: verifyFinishCommand,
20586
+ input: verifyInputCommand,
20587
+ render: verifyRenderCommand,
20588
+ start: verifyStartCommand,
20589
+ status: verifyStatusCommand
20590
+ };
20038
20591
  async function readStdinText() {
20039
20592
  const chunks = [];
20040
20593
  for await (const chunk of process.stdin) {
@@ -20049,30 +20602,43 @@ async function readCliSource(source) {
20049
20602
  var verifyDomain = {
20050
20603
  name: VERIFY_CLI.commandName,
20051
20604
  description: VERIFY_CLI.description,
20052
- register: (program, invocation) => {
20053
- const deps = () => ({
20054
- cwd: invocation.resolveEffectiveInvocationDir(),
20055
- readInputSource: readCliSource,
20056
- readPayloadSource: readCliSource,
20057
- // The append verbs write a single structured JSON result to stdout, so the run's event
20058
- // stream goes to stderr under the local backend rather than sharing the result channel.
20059
- journalBinding: createJournalStreamBinding(invocation.io, stderrStreamSink(invocation.io))
20060
- });
20061
- const command = program.command(VERIFY_CLI.commandName).description(VERIFY_CLI.description);
20062
- command.command(VERIFY_CLI.startCommandName).description("Start a changeset-scoped verification run and report its run locator").requiredOption(VERIFY_CLI.verificationTypeOption, "Verification type recorded for the run").requiredOption(VERIFY_CLI.scopeTypeOption, "Scope type; changeset").requiredOption(VERIFY_CLI.scopeOption, "Changeset scope as <base>..<head>").requiredOption(VERIFY_CLI.inputOption, "Verification input source; stdin or a file path").action(async (options) => {
20063
- reportCliResult(await verifyStartCommand(options, deps()), invocation.io);
20064
- });
20065
- command.command(VERIFY_CLI.inputCommandName).description("Replay the verification input recorded at start").requiredOption(VERIFY_CLI.verificationTypeOption, "Verification type recorded for the run").requiredOption(VERIFY_CLI.scopeTypeOption, "Scope type; changeset").requiredOption(VERIFY_CLI.scopeOption, "Changeset scope as <base>..<head>").requiredOption(VERIFY_CLI.runOption, "Run token reported by start").action(async (options) => {
20066
- reportCliResult(await verifyInputCommand(options, deps()), invocation.io);
20067
- });
20068
- command.command(VERIFY_CLI.appendScopeCommandName).description("Record the inspected scope for a started run").requiredOption(VERIFY_CLI.verificationTypeOption, "Verification type recorded for the run").requiredOption(VERIFY_CLI.scopeTypeOption, "Scope type; changeset").requiredOption(VERIFY_CLI.scopeOption, "Changeset scope as <base>..<head>").requiredOption(VERIFY_CLI.runOption, "Run token reported by start").requiredOption(VERIFY_CLI.payloadOption, "Append payload source; stdin or a file path").requiredOption(VERIFY_CLI.idempotencyKeyOption, "Caller-supplied idempotency key for the append").action(async (options) => {
20069
- reportCliResult(await verifyAppendScopeCommand(options, deps()), invocation.io);
20070
- });
20071
- command.command(VERIFY_CLI.appendFindingCommandName).description("Record a validated verification finding for a started run").requiredOption(VERIFY_CLI.verificationTypeOption, "Verification type recorded for the run").requiredOption(VERIFY_CLI.scopeTypeOption, "Scope type; changeset").requiredOption(VERIFY_CLI.scopeOption, "Changeset scope as <base>..<head>").requiredOption(VERIFY_CLI.runOption, "Run token reported by start").requiredOption(VERIFY_CLI.payloadOption, "Append payload source; stdin or a file path").requiredOption(VERIFY_CLI.idempotencyKeyOption, "Caller-supplied idempotency key for the append").action(async (options) => {
20072
- reportCliResult(await verifyAppendFindingCommand(options, deps()), invocation.io);
20073
- });
20074
- }
20075
- };
20605
+ register: (program, invocation) => registerVerifyCommands(program, invocation)
20606
+ };
20607
+ function registerVerifyCommands(program, invocation, handlers = DEFAULT_VERIFY_CLI_HANDLERS) {
20608
+ const deps = () => ({
20609
+ cwd: invocation.resolveEffectiveInvocationDir(),
20610
+ readInputSource: readCliSource,
20611
+ readPayloadSource: readCliSource,
20612
+ // The append verbs write a single structured JSON result to stdout, so the run's event
20613
+ // stream goes to stderr under the local backend rather than sharing the result channel.
20614
+ journalBinding: createJournalStreamBinding(invocation.io, stderrStreamSink(invocation.io))
20615
+ });
20616
+ const command = program.command(VERIFY_CLI.commandName).description(VERIFY_CLI.description);
20617
+ const runCommand = command.command(VERIFY_CLI.runCommandName).description("Manage a typed verification run lifecycle");
20618
+ runCommand.command(VERIFY_CLI.startCommandName).description("Start a changeset-scoped verification run and report its run locator").requiredOption(VERIFY_CLI.verificationTypeOption, "Verification type recorded for the run").requiredOption(VERIFY_CLI.scopeTypeOption, "Scope type; changeset").requiredOption(VERIFY_CLI.scopeOption, "Changeset scope as <base>..<head>").requiredOption(VERIFY_CLI.inputOption, "Verification input source; stdin or a file path").action(async (options) => {
20619
+ reportCliResult(await handlers.start(options, deps()), invocation.io);
20620
+ });
20621
+ runCommand.command(VERIFY_CLI.inputCommandName).description("Replay the verification input recorded at start").requiredOption(VERIFY_CLI.verificationTypeOption, "Verification type recorded for the run").requiredOption(VERIFY_CLI.scopeTypeOption, "Scope type; changeset").requiredOption(VERIFY_CLI.scopeOption, "Changeset scope as <base>..<head>").requiredOption(VERIFY_CLI.runOption, "Run token reported by start").action(async (options) => {
20622
+ reportCliResult(await handlers.input(options, deps()), invocation.io);
20623
+ });
20624
+ const scopeCommand = runCommand.command(VERIFY_CLI.scopeCommandName).description("Manage inspected scope evidence for a started verification run");
20625
+ scopeCommand.command(VERIFY_CLI.addCommandName).description("Record the inspected scope for a started run").requiredOption(VERIFY_CLI.verificationTypeOption, "Verification type recorded for the run").requiredOption(VERIFY_CLI.scopeTypeOption, "Scope type; changeset").requiredOption(VERIFY_CLI.scopeOption, "Changeset scope as <base>..<head>").requiredOption(VERIFY_CLI.runOption, "Run token reported by start").requiredOption(VERIFY_CLI.payloadOption, VERIFY_CLI.payloadOptionDescription).requiredOption(VERIFY_CLI.idempotencyKeyOption, VERIFY_CLI.idempotencyKeyOptionDescription).action(async (options) => {
20626
+ reportCliResult(await handlers.appendScope(options, deps()), invocation.io);
20627
+ });
20628
+ const findingCommand = runCommand.command(VERIFY_CLI.findingCommandName).description("Manage finding evidence for a started verification run");
20629
+ findingCommand.command(VERIFY_CLI.addCommandName).description("Record a validated verification finding for a started run").requiredOption(VERIFY_CLI.verificationTypeOption, "Verification type recorded for the run").requiredOption(VERIFY_CLI.scopeTypeOption, "Scope type; changeset").requiredOption(VERIFY_CLI.scopeOption, "Changeset scope as <base>..<head>").requiredOption(VERIFY_CLI.runOption, "Run token reported by start").requiredOption(VERIFY_CLI.payloadOption, VERIFY_CLI.payloadOptionDescription).requiredOption(VERIFY_CLI.idempotencyKeyOption, VERIFY_CLI.idempotencyKeyOptionDescription).action(async (options) => {
20630
+ reportCliResult(await handlers.appendFinding(options, deps()), invocation.io);
20631
+ });
20632
+ runCommand.command(VERIFY_CLI.finishCommandName).description("Record terminal completion, seal the run journal, and report its terminal projection").requiredOption(VERIFY_CLI.verificationTypeOption, "Verification type recorded for the run").requiredOption(VERIFY_CLI.scopeTypeOption, "Scope type; changeset").requiredOption(VERIFY_CLI.scopeOption, "Changeset scope as <base>..<head>").requiredOption(VERIFY_CLI.runOption, "Run token reported by start").requiredOption(VERIFY_CLI.terminalStatusOption, "Terminal status recorded before sealing").action(async (options) => {
20633
+ reportCliResult(await handlers.finish(options, deps()), invocation.io);
20634
+ });
20635
+ runCommand.command(VERIFY_CLI.statusCommandName).description("Report the run's resumable status projected from its journal history").requiredOption(VERIFY_CLI.verificationTypeOption, "Verification type recorded for the run").requiredOption(VERIFY_CLI.scopeTypeOption, "Scope type; changeset").requiredOption(VERIFY_CLI.scopeOption, "Changeset scope as <base>..<head>").requiredOption(VERIFY_CLI.runOption, "Run token reported by start").action(async (options) => {
20636
+ reportCliResult(await handlers.status(options, deps()), invocation.io);
20637
+ });
20638
+ runCommand.command(VERIFY_CLI.renderCommandName).description("Render the run's journal projection with its authoritative finding count").requiredOption(VERIFY_CLI.verificationTypeOption, "Verification type recorded for the run").requiredOption(VERIFY_CLI.scopeTypeOption, "Scope type; changeset").requiredOption(VERIFY_CLI.scopeOption, "Changeset scope as <base>..<head>").requiredOption(VERIFY_CLI.runOption, "Run token reported by start").action(async (options) => {
20639
+ reportCliResult(await handlers.render(options, deps()), invocation.io);
20640
+ });
20641
+ }
20076
20642
 
20077
20643
  // src/interfaces/cli/worktree.ts
20078
20644
  import { randomBytes as nodeRandomBytes4 } from "crypto";