@empire-builder-kit/nx 1.0.0-rc.41 → 1.0.0-rc.43

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@empire-builder-kit/nx",
3
- "version": "1.0.0-rc.41",
3
+ "version": "1.0.0-rc.43",
4
4
  "description": "Nx generators, executors, and Blueprint contracts for Empire Builder Kit",
5
5
  "keywords": [
6
6
  "aws",
@@ -145,12 +145,12 @@
145
145
  "provenance": false
146
146
  },
147
147
  "dependencies": {
148
- "@empire-builder-kit/runtime": "1.0.0-rc.41",
148
+ "@empire-builder-kit/runtime": "1.0.0-rc.43",
149
149
  "@aws-sdk/client-cloudfront": "^3.700.0",
150
150
  "@aws-sdk/client-cloudwatch": "^3.700.0",
151
151
  "@aws-sdk/client-cloudwatch-logs": "^3.700.0",
152
152
  "@aws-sdk/client-iam": "^3.700.0",
153
- "@nx/devkit": "23.1.0",
153
+ "@nx/devkit": "23.2.1",
154
154
  "@aws-sdk/client-rds": "^3.700.0",
155
155
  "@aws-sdk/client-s3": "^3.700.0",
156
156
  "@aws-sdk/client-secrets-manager": "^3.700.0",
@@ -159,7 +159,7 @@
159
159
  "@aws-sdk/client-wafv2": "^3.700.0",
160
160
  "@aws-sdk/credential-providers": "^3.700.0",
161
161
  "ajv": "8.20.0",
162
- "nx": "23.1.0",
162
+ "nx": "23.2.1",
163
163
  "prettier": "^3.8.2",
164
164
  "semver": "7.7.4",
165
165
  "tslib": "^2.3.0",
@@ -178,5 +178,5 @@
178
178
  "engines": {
179
179
  "node": ">=22.13.0"
180
180
  },
181
- "gitHead": "6c8e932ac3acf0a1e124f7687cd7b8582b864c49"
181
+ "gitHead": "7703b131fbcd59bc364ce7f914a1ff22250378c9"
182
182
  }
@@ -259,7 +259,11 @@ async function executeDeveloperAgentCommand(command, workspaceRoot, overrides =
259
259
  ...overrides,
260
260
  };
261
261
  if (command.command === 'context') {
262
- const context = await dependencies.inspectContext(workspaceRoot, command.query);
262
+ const inspected = await dependencies.inspectContext(workspaceRoot, command.query);
263
+ // The context leaves the process here, so this is where the transport
264
+ // byte bound applies; plan, upgrade preview and resume keep the complete
265
+ // in-process context.
266
+ const { context } = (0, context_js_1.serializeDeveloperAgentContextForTransport)(inspected);
263
267
  const blockingClassification = model_js_1.DEVELOPER_AGENT_CONTEXT_BLOCKING_FAILURES.find((classification) => context.warnings.some((warning) => warning.classification === classification));
264
268
  return {
265
269
  human: (0, context_js_1.formatDeveloperAgentContext)(context),
@@ -446,6 +450,12 @@ async function executeDeveloperAgentCommand(command, workspaceRoot, overrides =
446
450
  };
447
451
  }
448
452
  function classifyDeveloperAgentCliError(error) {
453
+ // Must precede the generic DeveloperAgentContextError branch, which would
454
+ // redact this tool-internal-error message. The bound message carries only
455
+ // counts, byte sizes and the selection remedy.
456
+ if (error instanceof context_js_1.DeveloperAgentContextBoundError) {
457
+ return { classification: error.classification, message: error.message };
458
+ }
449
459
  if (error instanceof DeveloperAgentCliError ||
450
460
  error instanceof context_js_1.DeveloperAgentContextError ||
451
461
  error instanceof handoff_js_1.DeveloperAgentHandoffError ||
@@ -9,7 +9,15 @@ export interface DeployResultProject {
9
9
  }
10
10
  /** The development-only `--json` result: the executor's own written receipt
11
11
  * (already secret-free evidence) and per-project status. It is printed after
12
- * the run and carries no authority; protected stages never produce it. */
12
+ * the run and carries no authority; protected stages never produce it.
13
+ *
14
+ * `outcome` and `exitCode` are two independent facts and are never
15
+ * reconciled. `outcome` is the verdict of this run's fresh receipt (or
16
+ * `receipt-unavailable`); `exitCode` is the lifecycle process's own exit
17
+ * status, 128 plus the signal number when it was signalled, and is also the
18
+ * command's exit status. They can disagree, for example when the process is
19
+ * interrupted after the executor wrote a `succeeded` receipt. Success
20
+ * requires both `exitCode` 0 and `outcome` `succeeded`. */
13
21
  export interface DeployJsonResult {
14
22
  exitCode: number;
15
23
  kind: typeof DEPLOY_RESULT_KIND;
@@ -22,6 +22,36 @@ export declare class DeveloperAgentContextError extends Error {
22
22
  readonly classification: DeveloperAgentFailureClassification;
23
23
  constructor(classification: DeveloperAgentFailureClassification, message: string);
24
24
  }
25
+ /**
26
+ * The framework produced a context larger than its fixed transport bound even
27
+ * after bounding the concurrent-change list. This is not a missing local
28
+ * prerequisite and not caller input: the message carries only counts and
29
+ * byte sizes (never paths) plus the selection remedy, so the CLI may pass it
30
+ * through instead of the generic internal-failure text.
31
+ */
32
+ export declare class DeveloperAgentContextBoundError extends DeveloperAgentContextError {
33
+ constructor(message: string);
34
+ }
25
35
  export declare function resolveAffectedProjectsWithNx(workspaceRoot: string, baseCommit: string, headCommit: string): Promise<readonly string[]>;
26
36
  export declare function inspectDeveloperAgentContext(cwd: string, query?: DeveloperAgentContextQuery, dependencyOverrides?: Partial<DeveloperAgentContextDependencies>): Promise<DeveloperAgentContext>;
37
+ /**
38
+ * Projects the complete in-process context into its emitted form. When the
39
+ * complete context fits the transport bound it is emitted unchanged apart
40
+ * from the summary fields. Otherwise concurrent changes inside the
41
+ * selection's coverage are retained first, then the rest, filling the byte
42
+ * budget exactly; the retained subset stays in canonical order and the
43
+ * count, digest and truncation flag describe the complete list. A context
44
+ * that cannot fit even with every concurrent change omitted raises
45
+ * DeveloperAgentContextBoundError.
46
+ */
47
+ export declare function projectDeveloperAgentContextForTransport(context: DeveloperAgentContext): DeveloperAgentContext;
48
+ /**
49
+ * The single emit-point serializer: projects, then serializes under the
50
+ * transport bound. Every emitted context (CLI JSON payload and executor
51
+ * fingerprint) goes through here.
52
+ */
53
+ export declare function serializeDeveloperAgentContextForTransport(context: DeveloperAgentContext): {
54
+ context: DeveloperAgentContext;
55
+ serialized: string;
56
+ };
27
57
  export declare function formatDeveloperAgentContext(context: DeveloperAgentContext): string;
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DeveloperAgentContextError = exports.DEVELOPER_AGENT_CONTEXT_HUMAN_MAX_BYTES = void 0;
3
+ exports.DeveloperAgentContextBoundError = exports.DeveloperAgentContextError = exports.DEVELOPER_AGENT_CONTEXT_HUMAN_MAX_BYTES = void 0;
4
4
  exports.resolveAffectedProjectsWithNx = resolveAffectedProjectsWithNx;
5
5
  exports.inspectDeveloperAgentContext = inspectDeveloperAgentContext;
6
+ exports.projectDeveloperAgentContextForTransport = projectDeveloperAgentContextForTransport;
7
+ exports.serializeDeveloperAgentContextForTransport = serializeDeveloperAgentContextForTransport;
6
8
  exports.formatDeveloperAgentContext = formatDeveloperAgentContext;
7
9
  const devkit_1 = require("@nx/devkit");
8
10
  const node_child_process_1 = require("node:child_process");
@@ -10,11 +12,13 @@ const node_crypto_1 = require("node:crypto");
10
12
  const node_fs_1 = require("node:fs");
11
13
  const node_path_1 = require("node:path");
12
14
  const node_util_1 = require("node:util");
15
+ const canonical_json_1 = require("@empire-builder-kit/runtime/canonical-json");
13
16
  const nx_workspace_js_1 = require("../local-dev/nx-workspace.js");
14
17
  const resolver_js_1 = require("../blueprints/resolver.js");
15
18
  const blueprint_trust_git_js_1 = require("../supply-chain/blueprint-trust-git.js");
16
19
  const blueprint_verification_authority_js_1 = require("../supply-chain/blueprint-verification-authority.js");
17
20
  const git_inspection_js_1 = require("./git-inspection.js");
21
+ const model_js_1 = require("./model.js");
18
22
  const ordering_js_1 = require("./ordering.js");
19
23
  const validation_js_1 = require("./validation.js");
20
24
  const workspace_inspection_js_1 = require("./workspace-inspection.js");
@@ -43,6 +47,20 @@ class DeveloperAgentContextError extends Error {
43
47
  }
44
48
  }
45
49
  exports.DeveloperAgentContextError = DeveloperAgentContextError;
50
+ /**
51
+ * The framework produced a context larger than its fixed transport bound even
52
+ * after bounding the concurrent-change list. This is not a missing local
53
+ * prerequisite and not caller input: the message carries only counts and
54
+ * byte sizes (never paths) plus the selection remedy, so the CLI may pass it
55
+ * through instead of the generic internal-failure text.
56
+ */
57
+ class DeveloperAgentContextBoundError extends DeveloperAgentContextError {
58
+ constructor(message) {
59
+ super('tool-internal-error', message);
60
+ this.name = 'DeveloperAgentContextBoundError';
61
+ }
62
+ }
63
+ exports.DeveloperAgentContextBoundError = DeveloperAgentContextBoundError;
46
64
  function contained(root, path) {
47
65
  const fromRoot = (0, node_path_1.relative)(root, path);
48
66
  return (fromRoot === '' ||
@@ -178,10 +196,24 @@ async function resolveAffectedProjectsWithNx(workspaceRoot, baseCommit, headComm
178
196
  return fail('prerequisite-missing', 'Workspace-pinned Nx affected inspection failed for the exact base/head pair.');
179
197
  }
180
198
  }
199
+ function concurrentChangeSummary(concurrentChanges) {
200
+ return {
201
+ concurrentChangeCount: concurrentChanges.length,
202
+ concurrentChangesDigest: `sha256:${(0, node_crypto_1.createHash)('sha256')
203
+ .update((0, canonical_json_1.canonicalJson)(concurrentChanges), 'utf8')
204
+ .digest('hex')}`,
205
+ };
206
+ }
181
207
  function gitContext(inspection) {
182
208
  if (!inspection.defaultBase) {
183
209
  return fail('tool-internal-error', 'Git inspection omitted the workspace default-base binding.');
184
210
  }
211
+ const concurrentChanges = inspection.concurrentChanges.map(({ committed, dirty, path, worktreeRoots }) => ({
212
+ committed,
213
+ dirty,
214
+ path,
215
+ worktreeCount: worktreeRoots.length,
216
+ }));
185
217
  return {
186
218
  ahead: inspection.worktree.upstream?.ahead ?? 0,
187
219
  ...(inspection.comparison
@@ -192,13 +224,10 @@ function gitContext(inspection) {
192
224
  ? { branch: inspection.worktree.branch }
193
225
  : {}),
194
226
  commit: inspection.worktree.commit,
227
+ ...concurrentChangeSummary(concurrentChanges),
195
228
  concurrentChangeInspectionComplete: inspection.concurrentChangeInspectionComplete,
196
- concurrentChanges: inspection.concurrentChanges.map(({ committed, dirty, path, worktreeRoots }) => ({
197
- committed,
198
- dirty,
199
- path,
200
- worktreeCount: worktreeRoots.length,
201
- })),
229
+ concurrentChanges,
230
+ concurrentChangesTruncated: false,
202
231
  defaultBaseCommit: inspection.defaultBase.commit,
203
232
  defaultBaseRef: inspection.defaultBase.ref,
204
233
  dirty: inspection.worktree.dirtyPaths.length > 0,
@@ -308,6 +337,12 @@ function boundedAffectedFiles(paths) {
308
337
  function classifyWorkspaceFailure(error) {
309
338
  if (error instanceof DeveloperAgentContextError)
310
339
  throw error;
340
+ if (error instanceof validation_js_1.DeveloperAgentContractValidationError) {
341
+ // The framework assembled a context that breaks its own contract. That is
342
+ // neither caller input nor a missing local prerequisite, so it must not
343
+ // reach the message-pattern classification below.
344
+ return fail('tool-internal-error', INTERNAL_CONTEXT_FAILURE_MESSAGE);
345
+ }
311
346
  const message = error instanceof Error ? error.message : String(error);
312
347
  if (/policy/i.test(message)) {
313
348
  return fail('policy-conflict', 'Developer-Agent workspace policy inspection reported a conflict.');
@@ -445,13 +480,111 @@ async function inspectDeveloperAgentContext(cwd, query = {}, dependencyOverrides
445
480
  ? 'dirty-worktree'
446
481
  : 'stale-base', 'Git state changed while Developer-Agent context was inspected.');
447
482
  }
448
- (0, validation_js_1.serializeDeveloperAgentContext)(result);
449
- return result;
483
+ // Structure only: the transport byte bound applies where the context
484
+ // leaves the process (serializeDeveloperAgentContextForTransport), so
485
+ // plan, upgrade preview and handoff resume keep every inspected fact.
486
+ return (0, validation_js_1.assertDeveloperAgentContextStructure)(result);
450
487
  }
451
488
  catch (error) {
452
489
  return classifyWorkspaceFailure(error);
453
490
  }
454
491
  }
492
+ function canonicalByteLength(value) {
493
+ return Buffer.byteLength((0, canonical_json_1.canonicalJson)(value), 'utf8');
494
+ }
495
+ function withConcurrentChanges(context, concurrentChanges, complete, truncated) {
496
+ return {
497
+ ...context,
498
+ git: {
499
+ ...context.git,
500
+ ...concurrentChangeSummary(complete),
501
+ concurrentChanges,
502
+ concurrentChangesTruncated: truncated,
503
+ },
504
+ };
505
+ }
506
+ /**
507
+ * Paths most relevant to the selection: anything under a selected project's
508
+ * root, root-level files, and the workspace `.ebk/` directory. A project
509
+ * rooted at the workspace root contributes only those root-level paths rather
510
+ * than the whole tree.
511
+ */
512
+ function concurrentChangeCoverage(context) {
513
+ const prefixes = context.projects
514
+ .map(({ root }) => root.replace(/\/+$/, ''))
515
+ .filter((root) => root !== '' && root !== '.')
516
+ .map((root) => `${root}/`);
517
+ return (path) => !path.includes('/') ||
518
+ path.startsWith('.ebk/') ||
519
+ prefixes.some((prefix) => path.startsWith(prefix));
520
+ }
521
+ function contextBoundError(context, projectedBytes, retainedConcurrentChanges) {
522
+ const complete = context.git.concurrentChanges;
523
+ return new DeveloperAgentContextBoundError(`Developer-Agent context exceeds its ${model_js_1.DEVELOPER_AGENT_CONTEXT_MAX_BYTES}-byte transport bound: it needs ${projectedBytes} canonical bytes with ${retainedConcurrentChanges} of ${complete.length} concurrent change(s) retained (${canonicalByteLength(complete)} bytes complete); ${context.supportedOperations.length} supported operation(s) use ${canonicalByteLength(context.supportedOperations)} bytes. Narrow the selection with --projects=<slice[,slice]>.`);
524
+ }
525
+ /**
526
+ * Projects the complete in-process context into its emitted form. When the
527
+ * complete context fits the transport bound it is emitted unchanged apart
528
+ * from the summary fields. Otherwise concurrent changes inside the
529
+ * selection's coverage are retained first, then the rest, filling the byte
530
+ * budget exactly; the retained subset stays in canonical order and the
531
+ * count, digest and truncation flag describe the complete list. A context
532
+ * that cannot fit even with every concurrent change omitted raises
533
+ * DeveloperAgentContextBoundError.
534
+ */
535
+ function projectDeveloperAgentContextForTransport(context) {
536
+ const complete = context.git.concurrentChanges;
537
+ const untruncated = withConcurrentChanges(context, complete, complete, false);
538
+ if (canonicalByteLength(untruncated) <= model_js_1.DEVELOPER_AGENT_CONTEXT_MAX_BYTES) {
539
+ return untruncated;
540
+ }
541
+ // Measure the empty projection with the longer `false` literal so that a
542
+ // budget which admits every entry is impossible here: the complete list
543
+ // was already shown not to fit, so the emitted flag is always honest.
544
+ const minimumBytes = canonicalByteLength(withConcurrentChanges(context, [], complete, false));
545
+ if (minimumBytes > model_js_1.DEVELOPER_AGENT_CONTEXT_MAX_BYTES) {
546
+ throw contextBoundError(context, minimumBytes, 0);
547
+ }
548
+ const covered = concurrentChangeCoverage(context);
549
+ const prioritized = [
550
+ ...complete.filter(({ path }) => covered(path)),
551
+ ...complete.filter(({ path }) => !covered(path)),
552
+ ];
553
+ const retained = new Set();
554
+ let remaining = model_js_1.DEVELOPER_AGENT_CONTEXT_MAX_BYTES - minimumBytes;
555
+ for (const entry of prioritized) {
556
+ const cost = canonicalByteLength(entry) + (retained.size > 0 ? 1 : 0);
557
+ if (cost > remaining)
558
+ break;
559
+ remaining -= cost;
560
+ retained.add(entry);
561
+ }
562
+ return withConcurrentChanges(context, complete.filter((entry) => retained.has(entry)), complete, true);
563
+ }
564
+ /**
565
+ * The single emit-point serializer: projects, then serializes under the
566
+ * transport bound. Every emitted context (CLI JSON payload and executor
567
+ * fingerprint) goes through here.
568
+ */
569
+ function serializeDeveloperAgentContextForTransport(context) {
570
+ const projected = projectDeveloperAgentContextForTransport(context);
571
+ try {
572
+ return {
573
+ context: projected,
574
+ serialized: (0, validation_js_1.serializeDeveloperAgentContext)(projected),
575
+ };
576
+ }
577
+ catch (error) {
578
+ if (error instanceof validation_js_1.DeveloperAgentContractValidationError) {
579
+ if (error.issues.length > 0 &&
580
+ error.issues.every(({ message, path }) => path === '$' && message.startsWith('must serialize to at most'))) {
581
+ throw contextBoundError(context, canonicalByteLength(projected), projected.git.concurrentChanges.length);
582
+ }
583
+ return fail('tool-internal-error', INTERNAL_CONTEXT_FAILURE_MESSAGE);
584
+ }
585
+ throw error;
586
+ }
587
+ }
455
588
  function formatDeveloperAgentContext(context) {
456
589
  const branch = context.git.branch ?? '(detached)';
457
590
  const selection = context.selection.slice
@@ -207,6 +207,19 @@ function inspectRetainedEvidence(workspaceRoot, handoff) {
207
207
  function invalidInput(message) {
208
208
  throw new DeveloperAgentHandoffError('invalid-input', message);
209
209
  }
210
+ /** Issue paths named in one invalid-handoff message; the rest are counted. */
211
+ const MAX_REPORTED_HANDOFF_ISSUE_PATHS = 16;
212
+ /**
213
+ * Names every distinct validation issue path, sorted and bounded, so one
214
+ * attempt surfaces all refusals. Paths carry only fixed property names and
215
+ * array indexes (unknown properties are reported as `.*`), never values.
216
+ */
217
+ function invalidHandoff(issues) {
218
+ const paths = [...new Set(issues.map(({ path }) => path))].sort(ordering_js_1.compareDeveloperAgentStrings);
219
+ const reported = paths.slice(0, MAX_REPORTED_HANDOFF_ISSUE_PATHS);
220
+ const omitted = paths.length - reported.length;
221
+ return invalidInput(`Retained Developer-Agent handoff is invalid at ${reported.length > 0 ? reported.join(', ') : '$'}${omitted > 0 ? ` and ${omitted} more issue paths` : ''}.`);
222
+ }
210
223
  function compareStrings(left, right) {
211
224
  if (left.length !== right.length)
212
225
  return false;
@@ -331,8 +344,7 @@ function parseDeveloperAgentHandoff(serialized) {
331
344
  }
332
345
  const validation = (0, validation_js_1.validateDeveloperAgentHandoff)(value);
333
346
  if (!validation.valid) {
334
- const firstPath = validation.issues[0]?.path ?? '$';
335
- return invalidInput(`Retained Developer-Agent handoff is invalid at ${firstPath}.`);
347
+ return invalidHandoff(validation.issues);
336
348
  }
337
349
  const handoff = validation.value;
338
350
  const sentinelPath = /^(?:0{40}|0{64})$/.test(handoff.repository.baseCommit)
@@ -368,10 +380,13 @@ function parseDeveloperAgentHandoff(serialized) {
368
380
  function assessDeveloperAgentHandoffResumeInternal(handoff, current, evidenceInspection) {
369
381
  const validation = (0, validation_js_1.validateDeveloperAgentHandoff)(handoff);
370
382
  if (!validation.valid) {
371
- const firstPath = validation.issues[0]?.path ?? '$';
372
- return invalidInput(`Retained Developer-Agent handoff is invalid at ${firstPath}.`);
383
+ return invalidHandoff(validation.issues);
373
384
  }
374
- const contextValidation = (0, validation_js_1.validateDeveloperAgentContext)(current);
385
+ // Resume consumes the in-process context and never emits it, so only its
386
+ // structure is checked; the transport byte bound applies at emit points.
387
+ const contextValidation = (0, validation_js_1.validateDeveloperAgentContext)(current, {
388
+ transportBound: false,
389
+ });
375
390
  if (!contextValidation.valid) {
376
391
  return createAssessment(validation.value, [
377
392
  {
@@ -161,8 +161,18 @@ export interface DeveloperAgentGitContext {
161
161
  branch?: string;
162
162
  /** Null means a verified unborn symbolic branch, never a placeholder SHA. */
163
163
  commit: string | null;
164
+ /** Number of entries in the complete inspected concurrent-change list. */
165
+ concurrentChangeCount?: number;
164
166
  concurrentChangeInspectionComplete: boolean;
167
+ /**
168
+ * The in-process context carries every inspected entry. An emitted context
169
+ * may carry a byte-bounded subset; count, digest and truncation then
170
+ * describe the complete list.
171
+ */
165
172
  concurrentChanges: readonly DeveloperAgentConcurrentChangeContext[];
173
+ /** SHA-256 of the canonical JSON of the complete concurrent-change list. */
174
+ concurrentChangesDigest?: string;
175
+ concurrentChangesTruncated?: boolean;
166
176
  /** An implicit base may be absent during local first-use orientation. */
167
177
  defaultBaseCommit: string | null;
168
178
  defaultBaseRef: string;
@@ -603,9 +603,11 @@ const TARGET_RULES = [
603
603
  matches: exact('github-bootstrap', 'secret-initialize'),
604
604
  requiredVerification: ['source', 'protected-proof'],
605
605
  },
606
- // Report-first plans, collectors and deployed verification read and
607
- // propose only; staging and production reach stays bounded by the
608
- // deployment role, not by a plan-time approval point.
606
+ // Report-first plans and collectors read and propose only. Deployed
607
+ // verification (smoke-deployed) is bounded synthetic verification that
608
+ // creates and deletes synthetic users and data. Staging and production
609
+ // reach stays bounded by the deployment role, not by a plan-time approval
610
+ // point.
609
611
  {
610
612
  approvalRequired: false,
611
613
  authority: 'protected-propose',
@@ -144,6 +144,14 @@ function concurrentPathBlockers(context, plannedPaths, readOnly) {
144
144
  sanitizedSummary: 'Committed-path inspection for another linked worktree is incomplete, so mutation planning cannot exclude an overlap.',
145
145
  });
146
146
  }
147
+ if (context.git.concurrentChangesTruncated === true) {
148
+ // Planning consumes the complete in-process context. A byte-bounded
149
+ // emitted projection cannot exclude an overlap, so it fails closed.
150
+ blockers.push({
151
+ classification: 'stale-base',
152
+ sanitizedSummary: 'The concurrent-change list is a truncated transport projection, so mutation planning cannot exclude an overlap.',
153
+ });
154
+ }
147
155
  const plannedKeys = new Set(plannedPaths.map((path) => path.normalize('NFC').toLocaleLowerCase('en-US')));
148
156
  const overlaps = (context.git.concurrentChanges ?? []).filter(({ path }) => plannedKeys.has(path.normalize('NFC').toLocaleLowerCase('en-US')));
149
157
  if (overlaps.length > 0) {
@@ -4,13 +4,25 @@ export declare class DeveloperAgentContractValidationError extends Error {
4
4
  readonly issues: readonly ValidationIssue[];
5
5
  constructor(label: string, issues: readonly ValidationIssue[]);
6
6
  }
7
- export declare function validateDeveloperAgentContext(value: unknown): ValidationResult<DeveloperAgentContext>;
7
+ export interface DeveloperAgentContextValidationOptions {
8
+ /**
9
+ * The byte bound applies where a context leaves the process (the context
10
+ * CLI JSON payload and the developer-context executor). The in-process
11
+ * context that planning, upgrade preview and handoff resume consume keeps
12
+ * every inspected fact and is validated for structure only.
13
+ */
14
+ transportBound?: boolean;
15
+ }
16
+ export declare function validateDeveloperAgentContext(value: unknown, options?: DeveloperAgentContextValidationOptions): ValidationResult<DeveloperAgentContext>;
8
17
  export declare function validateDeveloperAgentGeneratorPreview(value: unknown): ValidationResult<DeveloperAgentGeneratorPreview>;
9
18
  export declare function validateDeveloperAgentGeneratorPreviewReceipt(value: unknown): ValidationResult<DeveloperAgentGeneratorPreviewReceipt>;
10
19
  export declare function validateDeveloperAgentPlan(value: unknown): ValidationResult<DeveloperAgentPlan>;
11
20
  export declare function validateDeveloperAgentResult(value: unknown): ValidationResult<DeveloperAgentResult>;
12
21
  export declare function validateDeveloperAgentHandoff(value: unknown): ValidationResult<DeveloperAgentHandoff>;
22
+ /** Serializes an emitted context and enforces its transport byte bound. */
13
23
  export declare function serializeDeveloperAgentContext(value: unknown): string;
24
+ /** Validates the in-process context for structure only and returns it. */
25
+ export declare function assertDeveloperAgentContextStructure(value: unknown): DeveloperAgentContext;
14
26
  export declare function serializeDeveloperAgentGeneratorPreview(value: unknown): string;
15
27
  export declare function serializeDeveloperAgentGeneratorPreviewReceipt(value: unknown): string;
16
28
  export declare function serializeDeveloperAgentPlan(value: unknown): string;
@@ -8,6 +8,7 @@ exports.validateDeveloperAgentPlan = validateDeveloperAgentPlan;
8
8
  exports.validateDeveloperAgentResult = validateDeveloperAgentResult;
9
9
  exports.validateDeveloperAgentHandoff = validateDeveloperAgentHandoff;
10
10
  exports.serializeDeveloperAgentContext = serializeDeveloperAgentContext;
11
+ exports.assertDeveloperAgentContextStructure = assertDeveloperAgentContextStructure;
11
12
  exports.serializeDeveloperAgentGeneratorPreview = serializeDeveloperAgentGeneratorPreview;
12
13
  exports.serializeDeveloperAgentGeneratorPreviewReceipt = serializeDeveloperAgentGeneratorPreviewReceipt;
13
14
  exports.serializeDeveloperAgentPlan = serializeDeveloperAgentPlan;
@@ -466,6 +467,37 @@ function validateOwnershipVersionStates(value, path, issues) {
466
467
  });
467
468
  return true;
468
469
  }
470
+ const CONCURRENT_CHANGE_SUMMARY_KEYS = [
471
+ 'concurrentChangeCount',
472
+ 'concurrentChangesDigest',
473
+ 'concurrentChangesTruncated',
474
+ ];
475
+ /**
476
+ * The complete concurrent-change list is summarized by an exact count, a
477
+ * digest and a truncation flag. The three fields appear together or not at
478
+ * all, and an emitted subset must state honestly that it is a subset.
479
+ */
480
+ function validateConcurrentChangeSummary(record, concurrentChanges, path, issues) {
481
+ const present = CONCURRENT_CHANGE_SUMMARY_KEYS.filter((key) => has(record, key));
482
+ if (present.length === 0)
483
+ return;
484
+ if (present.length !== CONCURRENT_CHANGE_SUMMARY_KEYS.length) {
485
+ addIssue(issues, path, 'must carry concurrentChangeCount, concurrentChangesDigest and concurrentChangesTruncated together');
486
+ return;
487
+ }
488
+ const countValid = requireInteger(record.concurrentChangeCount, `${path}.concurrentChangeCount`, issues, 0, MAX_CONCURRENT_PATHS);
489
+ requireSafeString(record.concurrentChangesDigest, `${path}.concurrentChangesDigest`, issues, { maximumLength: 71, pattern: SHA256_DIGEST_PATTERN });
490
+ const truncatedValid = requireBoolean(record.concurrentChangesTruncated, `${path}.concurrentChangesTruncated`, issues);
491
+ if (!countValid || !truncatedValid || !concurrentChanges)
492
+ return;
493
+ const count = record.concurrentChangeCount;
494
+ if ((record.concurrentChangesTruncated === false &&
495
+ count !== concurrentChanges.length) ||
496
+ (record.concurrentChangesTruncated === true &&
497
+ count <= concurrentChanges.length)) {
498
+ addIssue(issues, path, 'must retain an exact concurrent-change count and an honest truncation state');
499
+ }
500
+ }
469
501
  function validateGitContext(value, path, issues) {
470
502
  const record = requireRecord(value, path, [
471
503
  'ahead',
@@ -477,7 +509,15 @@ function validateGitContext(value, path, issues) {
477
509
  'defaultBaseRef',
478
510
  'dirty',
479
511
  'worktreeRoot',
480
- ], ['baseCommit', 'branch', 'headCommit', 'upstream'], issues);
512
+ ], [
513
+ 'baseCommit',
514
+ 'branch',
515
+ 'concurrentChangeCount',
516
+ 'concurrentChangesDigest',
517
+ 'concurrentChangesTruncated',
518
+ 'headCommit',
519
+ 'upstream',
520
+ ], issues);
481
521
  if (!record)
482
522
  return false;
483
523
  requireInteger(record.ahead, `${path}.ahead`, issues, 0, 1_000_000);
@@ -530,6 +570,7 @@ function validateGitContext(value, path, issues) {
530
570
  }
531
571
  requireInteger(concurrent.worktreeCount, `${entryPath}.worktreeCount`, issues, 1, 64);
532
572
  });
573
+ validateConcurrentChangeSummary(record, concurrentChanges, path, issues);
533
574
  if (record.defaultBaseCommit !== null) {
534
575
  requireSafeString(record.defaultBaseCommit, `${path}.defaultBaseCommit`, issues, {
535
576
  maximumLength: 64,
@@ -1023,9 +1064,11 @@ function validateSerializedBound(value, path, maximumBytes, issues) {
1023
1064
  addIssue(issues, path, 'must be canonically JSON serializable');
1024
1065
  }
1025
1066
  }
1026
- function validateDeveloperAgentContext(value) {
1067
+ function validateDeveloperAgentContext(value, options = {}) {
1027
1068
  const issues = [];
1028
- validateSerializedBound(value, '$', model_js_1.DEVELOPER_AGENT_CONTEXT_MAX_BYTES, issues);
1069
+ if (options.transportBound !== false) {
1070
+ validateSerializedBound(value, '$', model_js_1.DEVELOPER_AGENT_CONTEXT_MAX_BYTES, issues);
1071
+ }
1029
1072
  const record = requireRecord(value, '$', [
1030
1073
  'authenticatedTargets',
1031
1074
  'documentationPaths',
@@ -2297,8 +2340,15 @@ function validateHandoffCoherence(record, issues) {
2297
2340
  const affectedProjects = Array.isArray(affected?.projects)
2298
2341
  ? affected.projects.filter((value) => typeof value === 'string')
2299
2342
  : [];
2343
+ // Generated native producers record the workspace's exact
2344
+ // @empire-builder-kit/runtime pin, which the preset keeps equal to the
2345
+ // installed @empire-builder-kit/nx version; the package-qualified form
2346
+ // stays accepted for hand-composed evidence. Both are exact equality to
2347
+ // the retained framework version, so evidence from another candidate is
2348
+ // refused.
2300
2349
  if (testingSource &&
2301
2350
  typeof versions?.frameworkVersion === 'string' &&
2351
+ testingSource.frameworkCandidate !== versions.frameworkVersion &&
2302
2352
  testingSource.frameworkCandidate !==
2303
2353
  `@empire-builder-kit/nx@${versions.frameworkVersion}`) {
2304
2354
  addIssue(issues, `$.results[${index}].testingEvidence.result.source.frameworkCandidate`, 'must match the retained framework candidate');
@@ -2360,15 +2410,46 @@ function validateHandoffCoherence(record, issues) {
2360
2410
  testingEnvironment.stage !== versions.canonicalStage))) {
2361
2411
  addIssue(issues, `$.results[${index}].testingEvidence.result.environment.stage`, 'must be present for deployed proof and exactly match the retained canonical stage');
2362
2412
  }
2413
+ // Registered target operations scope requiredVerification by proof
2414
+ // class (for example local-integration), so their own native evidence is
2415
+ // bound by operation identity instead; proofLayer stays bound to the
2416
+ // retained required evidence below. Source-edit results keep the
2417
+ // project:target verification vocabulary.
2418
+ const resultOperation = (0, validation_js_1.isRecord)(result.operation)
2419
+ ? result.operation
2420
+ : undefined;
2421
+ const evidenceIsOwnOperation = typeof testingOperation?.project === 'string' &&
2422
+ typeof testingOperation.target === 'string' &&
2423
+ resultOperation?.name ===
2424
+ `target:${testingOperation.project}:${testingOperation.target}`;
2363
2425
  if (typeof testingOperation?.target === 'string' &&
2426
+ !evidenceIsOwnOperation &&
2364
2427
  !requiredVerification.some((value) => typeof value === 'string' &&
2365
2428
  (value === testingOperation.target ||
2366
2429
  value.endsWith(`:${testingOperation.target}`)))) {
2367
2430
  addIssue(issues, `$.results[${index}].testingEvidence.result.operation.target`, 'must be accounted for by retained required verification');
2368
2431
  }
2432
+ // An own registered target's proof class is bound to that operation's
2433
+ // retained evidence scope, never to a class another operation
2434
+ // contributes to the handoff-wide union. Resume separately requires the
2435
+ // retained scope to equal the current registry scope.
2436
+ const ownRetainedOperation = evidenceIsOwnOperation
2437
+ ? operations.find((operation) => operation.id === resultOperation?.name)
2438
+ : undefined;
2439
+ const ownRetainedScope = (0, validation_js_1.isRecord)(ownRetainedOperation?.scope)
2440
+ ? ownRetainedOperation.scope
2441
+ : undefined;
2442
+ const proofLayerScope = evidenceIsOwnOperation
2443
+ ? Array.isArray(ownRetainedScope?.evidenceClasses)
2444
+ ? ownRetainedScope.evidenceClasses
2445
+ : []
2446
+ : requiredEvidence;
2369
2447
  if (typeof testingOperation?.proofLayer === 'string' &&
2370
- !requiredEvidence.includes(testingOperation.proofLayer)) {
2371
- addIssue(issues, `$.results[${index}].testingEvidence.result.operation.proofLayer`, 'must be accounted for by retained required evidence');
2448
+ (!requiredEvidence.includes(testingOperation.proofLayer) ||
2449
+ !proofLayerScope.includes(testingOperation.proofLayer))) {
2450
+ addIssue(issues, `$.results[${index}].testingEvidence.result.operation.proofLayer`, evidenceIsOwnOperation
2451
+ ? 'must be accounted for by the retained evidence scope of its own operation'
2452
+ : 'must be accounted for by retained required evidence');
2372
2453
  }
2373
2454
  if (testingEvidence && testingResult) {
2374
2455
  const outerArtifacts = Array.isArray(result.artifacts)
@@ -2612,8 +2693,19 @@ function serializeValidated(label, value, validate) {
2612
2693
  }
2613
2694
  return (0, canonical_json_1.canonicalJson)(result.value);
2614
2695
  }
2696
+ /** Serializes an emitted context and enforces its transport byte bound. */
2615
2697
  function serializeDeveloperAgentContext(value) {
2616
- return serializeValidated('Developer-Agent context', value, validateDeveloperAgentContext);
2698
+ return serializeValidated('Developer-Agent context', value, (candidate) => validateDeveloperAgentContext(candidate));
2699
+ }
2700
+ /** Validates the in-process context for structure only and returns it. */
2701
+ function assertDeveloperAgentContextStructure(value) {
2702
+ const result = validateDeveloperAgentContext(value, {
2703
+ transportBound: false,
2704
+ });
2705
+ if (!result.valid) {
2706
+ throw new DeveloperAgentContractValidationError('Developer-Agent context', result.issues);
2707
+ }
2708
+ return result.value;
2617
2709
  }
2618
2710
  function serializeDeveloperAgentGeneratorPreview(value) {
2619
2711
  return serializeValidated('Developer-Agent generator preview', value, validateDeveloperAgentGeneratorPreview);
@@ -10,7 +10,6 @@ const node_buffer_1 = require("node:buffer");
10
10
  const node_crypto_1 = require("node:crypto");
11
11
  const context_js_1 = require("../../developer-agent/context.js");
12
12
  const model_js_1 = require("../../developer-agent/model.js");
13
- const validation_js_1 = require("../../developer-agent/validation.js");
14
13
  exports.DEVELOPER_AGENT_CONTEXT_FINGERPRINT_KIND = 'developer-agent-context-fingerprint';
15
14
  exports.DEVELOPER_AGENT_CONTEXT_FINGERPRINT_SCHEMA_VERSION = 1;
16
15
  exports.DEVELOPER_AGENT_CONTEXT_FINGERPRINT_MAX_BYTES = 1024;
@@ -34,7 +33,9 @@ async function inspectDeveloperContextExecutor(options, workspaceRoot, dependenc
34
33
  };
35
34
  }
36
35
  function serializeDeveloperAgentContextFingerprint(context) {
37
- const canonicalContext = (0, validation_js_1.serializeDeveloperAgentContext)(context);
36
+ // The fingerprint binds the emitted (byte-bounded) context, the same bytes
37
+ // `ebk agent context --json` returns.
38
+ const canonicalContext = (0, context_js_1.serializeDeveloperAgentContextForTransport)(context).serialized;
38
39
  const fingerprint = {
39
40
  canonicalContextBytes: node_buffer_1.Buffer.byteLength(canonicalContext, 'utf8'),
40
41
  canonicalContextDigest: `sha256:${(0, node_crypto_1.createHash)('sha256')
@@ -53,8 +54,7 @@ function serializeDeveloperAgentContextFingerprint(context) {
53
54
  function renderDeveloperContextExecutorOutput(context, json) {
54
55
  if (json)
55
56
  return serializeDeveloperAgentContextFingerprint(context);
56
- (0, validation_js_1.serializeDeveloperAgentContext)(context);
57
- return (0, context_js_1.formatDeveloperAgentContext)(context);
57
+ return (0, context_js_1.formatDeveloperAgentContext)((0, context_js_1.serializeDeveloperAgentContextForTransport)(context).context);
58
58
  }
59
59
  async function executeDeveloperContextExecutor(options, workspaceRoot, dependencies = {}) {
60
60
  const result = await inspectDeveloperContextExecutor(options, workspaceRoot, dependencies);
@@ -45,7 +45,17 @@ Read \`AGENTS.md\`, \`docs/architecture.md\` and the user's requested acceptance
45
45
  Run \`pnpm nx run ${name}:work-markers\`, then narrow discovery to the relevant
46
46
  Slice: \`ebk agent context --projects billing --json\` (replace billing with an
47
47
  actual project). Use \`--slice=billing --feature=<registered-id>\` when relevant.
48
- Without a known owner, inspect the project list first; do not read every target.
48
+ For cross-Slice work, name exactly the Slices in scope, such as
49
+ \`--projects=billing,notifications\`. Without a known owner, inspect the project
50
+ list first; do not read every target.
51
+
52
+ The emitted context has a fixed byte bound. A full-workspace context, run
53
+ without \`--projects\`, can exceed it in a workspace with three or more Slices.
54
+ An overflow fails as \`tool-internal-error\` and reports the byte counts. It is
55
+ not a missing prerequisite: narrow \`--projects\` instead of repairing installs,
56
+ stores or worktrees. When many linked worktrees exist, the emitted list of
57
+ concurrent changes may be truncated (\`concurrentChangesTruncated\`); \`ebk agent
58
+ plan\` still checks overlaps against the complete list.
49
59
 
50
60
  Find its \`.ebk/ownership.json\`, feature registry, published contracts and
51
61
  \`project.json\`. Managed mechanics change through their owning generator;
@@ -143,9 +143,13 @@ or IDE command) that only invokes these same commands with \`--json\` is permitt
143
143
  ## Canonical loop
144
144
 
145
145
  1. Run \`pnpm nx run ${name}:work-markers\`.
146
- 2. Run \`ebk agent context\` to inspect bounded repository, Git, project, ownership, policy,
147
- marker, contract, and verification facts. Select an explicit base and head when affected
148
- calculation is required. This CLI is the sole full structured context transport. The root
146
+ 2. Run \`ebk agent context --projects=<slice[,slice]>\`, naming the Slices in scope, to inspect
147
+ bounded repository, Git, project, ownership, policy, marker, contract, and verification
148
+ facts. A full-workspace context without \`--projects\` can exceed the fixed context byte
149
+ bound in a workspace with three or more Slices; that fails as \`tool-internal-error\` with
150
+ the byte counts and is fixed by narrowing the selection, not by repairing local setup.
151
+ Select an explicit base and head when affected calculation is required.
152
+ This CLI is the sole full structured context transport. The root
149
153
  \`pnpm nx run ${name}:developer-context\` target is a compatibility and graph-authority
150
154
  route: its human mode emits concise prose, and \`--json\` emits only the canonical context
151
155
  byte length and digest in a bounded fingerprint.
@@ -282,7 +286,12 @@ or prerequisite-layer drift remains blocking. Prerelease consumers regenerate cl
282
286
  instead of using this stable-line transition as beta compatibility.
283
287
 
284
288
  Bound inspection with \`--projects=<names>\`, \`--slice=<name>\`, and optional
285
- \`--feature=<name>\`. Affected inspection requires the complete
289
+ \`--feature=<name>\`. The emitted JSON context is at most 128 KiB. When linked worktrees
290
+ carry more concurrent changes than fit, it keeps those under the selected projects,
291
+ root-level files and \`.ebk/\` first, and reports \`concurrentChangeCount\`,
292
+ \`concurrentChangesDigest\` and \`concurrentChangesTruncated\` for the complete list.
293
+ \`ebk agent plan\`, \`ebk agent upgrade-preview\` and \`ebk agent resume\` always use the
294
+ complete in-process list. Affected inspection requires the complete
286
295
  \`--base=<ref> --head=<ref>\` pair; \`--targets=<names>\` is valid only with that pair.
287
296
  Reference an existing generated or separately retained candidate document explicitly with
288
297
  \`--candidate-provenance=<workspace-relative-path>\`; context records only its path and
@@ -167,7 +167,7 @@ const TOOL_VERSIONS = {
167
167
  eslintPluginJsxA11y: '6.10.2',
168
168
  globals: '^16.0.0',
169
169
  nodeTypes: '25.5.0',
170
- nx: '23.1.0',
170
+ nx: '23.2.1',
171
171
  pnpm: tool_versions_js_1.WORKSPACE_PNPM_VERSION,
172
172
  prettier: '^3.8.2',
173
173
  pulumiRandom: '4.21.1',
@@ -15,9 +15,9 @@ export declare const PRODUCT_VERSIONS: {
15
15
  readonly awsSqs: "^3.700.0";
16
16
  readonly awsSsm: "^3.700.0";
17
17
  readonly awsSts: "^3.700.0";
18
- readonly drizzle: "^0.45.1";
18
+ readonly drizzle: "^0.45.2";
19
19
  readonly drizzleKit: "^0.31.4";
20
- readonly next: "16.3.4";
20
+ readonly next: "16.3.6";
21
21
  readonly pg: "^8.22.0";
22
22
  readonly playwright: "^1.59.1";
23
23
  readonly react: "19.2.8";
@@ -25,9 +25,9 @@ exports.PRODUCT_VERSIONS = {
25
25
  awsSqs: '^3.700.0',
26
26
  awsSsm: '^3.700.0',
27
27
  awsSts: '^3.700.0',
28
- drizzle: '^0.45.1',
28
+ drizzle: '^0.45.2',
29
29
  drizzleKit: '^0.31.4',
30
- next: '16.3.4',
30
+ next: '16.3.6',
31
31
  pg: '^8.22.0',
32
32
  playwright: '^1.59.1',
33
33
  react: '19.2.8',
@@ -1,8 +1,8 @@
1
1
  export declare const GITHUB_ACTION_PINS: {
2
2
  readonly cache: {
3
3
  readonly action: "actions/cache";
4
- readonly sha: "0057852bfaa89a56745cba8c7296529d2fc39830";
5
- readonly tag: "v4.3.0";
4
+ readonly sha: "caa296126883cff596d87d8935842f9db880ef25";
5
+ readonly tag: "v5.1.0";
6
6
  };
7
7
  readonly checkout: {
8
8
  readonly action: "actions/checkout";
@@ -5,8 +5,8 @@ exports.pinnedAction = pinnedAction;
5
5
  exports.GITHUB_ACTION_PINS = {
6
6
  cache: {
7
7
  action: 'actions/cache',
8
- sha: '0057852bfaa89a56745cba8c7296529d2fc39830',
9
- tag: 'v4.3.0',
8
+ sha: 'caa296126883cff596d87d8935842f9db880ef25',
9
+ tag: 'v5.1.0',
10
10
  },
11
11
  checkout: {
12
12
  action: 'actions/checkout',
@@ -47,7 +47,7 @@ function validateOperation(value) {
47
47
  : '/validation/.git',
48
48
  heartbeatAt: '1970-01-01T00:00:00.000Z',
49
49
  identitySource: 'worktree-task',
50
- nxVersion: '23.1.0',
50
+ nxVersion: '23.2.1',
51
51
  operations: [value],
52
52
  origin: 'https://validation.ebk.localhost',
53
53
  resolvedStage: 'personal-validation',