@north-light/api 0.3.575 → 0.3.577

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.
@@ -1,5 +1,5 @@
1
1
  /** The contract level this build serves and requires. Written by `scripts/contract.mjs release`; never edit by hand. */
2
- export declare const CONTRACT_LEVEL = "1.37";
2
+ export declare const CONTRACT_LEVEL = "1.39";
3
3
  /** Request header naming the one contract level the caller needs, e.g. `1.3`. Optional: absent means no check. */
4
4
  export declare const REQUIRED_CONTRACT_HEADER = "Crouter-Required-Contract";
5
5
  /** Response header listing the contract levels the server serves, comma-separated, one per major, e.g. `1.3`. */
@@ -12,7 +12,7 @@
12
12
  //
13
13
  // PURITY (spec §3.1): Node built-ins + `src/api/*` only.
14
14
  /** The contract level this build serves and requires. Written by `scripts/contract.mjs release`; never edit by hand. */
15
- export const CONTRACT_LEVEL = '1.37';
15
+ export const CONTRACT_LEVEL = '1.39';
16
16
  /** Request header naming the one contract level the caller needs, e.g. `1.3`. Optional: absent means no check. */
17
17
  export const REQUIRED_CONTRACT_HEADER = 'Crouter-Required-Contract';
18
18
  /** Response header listing the contract levels the server serves, comma-separated, one per major, e.g. `1.3`. */
@@ -131,6 +131,9 @@ export interface InstallBundleArchiveOptions<M> {
131
131
  /** Checked against the validated archive before anything is written; a
132
132
  * returned message refuses the archive (a provider bundle naming another id). */
133
133
  accept?: (bundle: ValidatedBundle) => string | undefined;
134
+ /** Default true. False leaves the config version and state validator unwritten: the caller keeps the install record itself
135
+ * (a cloud role's deployment-wide provider bundle has no person whose settings could hold it). */
136
+ record?: boolean;
134
137
  }
135
138
  export interface InstalledBundleArchive<M> {
136
139
  manifest: PluginManifest;
@@ -140,9 +143,9 @@ export interface InstalledBundleArchive<M> {
140
143
  staged: M;
141
144
  }
142
145
  /**
143
- * Validate `raw`, stage it beside the package root, swap it in, then record
144
- * the config version and the state validator — in that order, never the
145
- * reverse. A failure before the swap leaves the previous package untouched.
146
+ * Validate `raw`, stage it beside the package root, swap it in, then (unless
147
+ * `record` is false) record the config version and state validator. A failure
148
+ * before the swap leaves the previous package untouched.
146
149
  * The caller holds the plugin's swap lock (`bundleSwapLockPath`).
147
150
  */
148
151
  export declare function installBundleArchive<M>(name: string, raw: Uint8Array, options: InstallBundleArchiveOptions<M>): Promise<InstalledBundleArchive<M>>;
@@ -517,9 +517,9 @@ export function writeStagedBundle(stagingRoot, manifest, bundle) {
517
517
  syncTree(stagingRoot);
518
518
  }
519
519
  /**
520
- * Validate `raw`, stage it beside the package root, swap it in, then record
521
- * the config version and the state validator — in that order, never the
522
- * reverse. A failure before the swap leaves the previous package untouched.
520
+ * Validate `raw`, stage it beside the package root, swap it in, then (unless
521
+ * `record` is false) record the config version and state validator. A failure
522
+ * before the swap leaves the previous package untouched.
523
523
  * The caller holds the plugin's swap lock (`bundleSwapLockPath`).
524
524
  */
525
525
  export async function installBundleArchive(name, raw, options) {
@@ -590,18 +590,15 @@ export async function installBundleArchive(name, raw, options) {
590
590
  finally {
591
591
  removePath(staging);
592
592
  }
593
- // Directory swap, then the config's version, then the state's validator — in
594
- // that order, and never the reverse. A process killed mid-sequence leaves the
595
- // ledger naming an OLDER generation than the disk, which the next check
596
- // repairs: the server 200s against a stale-or-absent validator, the hash says
597
- // the tree on disk is already that generation, and only the ledger is
598
- // rewritten. The forbidden state — a validator for a generation the disk does
599
- // not have, and so a permanent 304 against a package that was never unpacked
600
- // — is unreachable from this order.
601
- await ensureScopeInitialized(scope, scopeRootPath);
602
- await updateConfig(scope, (cfg) => {
603
- cfg.plugins[name] = { enabled: options.enable ? true : (cfg.plugins[name]?.enabled ?? true), version };
604
- });
605
- await recordCheckedGeneration(scope, name, options.etag, { swapped: true });
593
+ // For ordinary installs: directory swap, then config version, then state validator. A process killed mid-sequence leaves the
594
+ // ledger naming an older generation than the disk, which the next check repairs. A cloud-role provider bundle has no person's
595
+ // settings to update; its caller records the validator after the swap in the deployment's plugin directory instead.
596
+ if (options.record !== false) {
597
+ await ensureScopeInitialized(scope, scopeRootPath);
598
+ await updateConfig(scope, (cfg) => {
599
+ cfg.plugins[name] = { enabled: options.enable ? true : (cfg.plugins[name]?.enabled ?? true), version };
600
+ });
601
+ await recordCheckedGeneration(scope, name, options.etag, { swapped: true });
602
+ }
606
603
  return { manifest, validated, version, root, staged };
607
604
  }
package/dist/core/io.js CHANGED
@@ -360,7 +360,7 @@ export function apiErrorToCliError(err, next, options = {}) {
360
360
  ? `Wait ${provider.retryAfterS}s, then retry; do not retry sooner.`
361
361
  : err.status >= 500
362
362
  ? 'This is a crtr bug, not a bad call. Retry; if it persists, report it (the daemon log holds the full cause).'
363
- : err.status === 404
363
+ : err.status === 404 && provider.code === undefined
364
364
  ? 'List nodes with `crtr node inspect list`.'
365
365
  : 'Inspect the error and adjust the call. See -h for the schema.'),
366
366
  };
@@ -44,6 +44,10 @@ export declare function acknowledgeBrokerMail(nodeId: string, deliveries: MailRe
44
44
  export declare function recordBrokerTurn(nodeId: string, request: Omit<BrokerTurnRequest, 'expected_execution_id'>): Promise<BrokerTurnResultDTO>;
45
45
  export declare function mutateBrokerProviderRetry(nodeId: string, request: Omit<BrokerProviderRetryRequest, 'expected_execution_id'>): Promise<BrokerProviderRetryResultDTO>;
46
46
  export declare function mutateBrokerFault(nodeId: string, request: Omit<BrokerFaultRequest, 'expected_execution_id'>): Promise<BrokerFaultResultDTO>;
47
+ /** A conversation store that refused writes past its deadline: the node's durable fault, written (execution-fenced) before the broker
48
+ * exits so the control plane and a harness read the cause. The record is capped at 5 s: a failed or slow record is logged and the
49
+ * exit proceeds. */
50
+ export declare function recordConversationWriteFault(nodeId: string, error: unknown): Promise<void>;
47
51
  export declare function brokerRecovery(nodeId: string): Promise<RecoveryStateDTO>;
48
52
  /** Node-scoped fault writes are for non-broker clients such as attach transport.
49
53
  * Broker-owned recovery mutations use the execution-fenced wrappers above. */
@@ -7,6 +7,7 @@ import { localClient } from '../../../api/node-transport.js';
7
7
  import { ApiError } from '../../../api/errors.js';
8
8
  import { envExecutionId } from '../../../shared/env.js';
9
9
  import { brokerSignalClient } from './signal-stream.js';
10
+ import { emitEvent } from '../../events/emit.js';
10
11
  import { cachedBrokerNodeFields } from '../../../daemon/broker-signals/bus.js';
11
12
  import { fetchRunAttachments } from './attachments-fetch.js';
12
13
  import { reportRefNodeId } from '../../../shared/inbox-entry-body.js';
@@ -153,15 +154,27 @@ export function mutateBrokerProviderRetry(nodeId, request) {
153
154
  export function mutateBrokerFault(nodeId, request) {
154
155
  return daemonClient().mutateBrokerFault(nodeId, { ...request, expected_execution_id: brokerExecutionId() });
155
156
  }
157
+ /** A conversation store that refused writes past its deadline: the node's durable fault, written (execution-fenced) before the broker
158
+ * exits so the control plane and a harness read the cause. The record is capped at 5 s: a failed or slow record is logged and the
159
+ * exit proceeds. */
160
+ export async function recordConversationWriteFault(nodeId, error) {
161
+ const record = mutateBrokerFault(nodeId, {
162
+ op: 'record',
163
+ input: { link: 'daemon→node', op: 'conversation_write_failed', kind: 'other', retry: { disposition: 'manual' }, message: error instanceof Error ? error.message : String(error) },
164
+ });
165
+ await Promise.race([record, new Promise((_, reject) => setTimeout(() => reject(new Error('conversation_write_failed fault record timed out')), 5_000).unref())])
166
+ .catch((faultError) => emitEvent({ level: 'error', event: 'broker.daemon.operation.failed', node_id: nodeId, error: faultError, fields: { operation: 'conversation_write_fault_record' } }));
167
+ }
156
168
  export function brokerRecovery(nodeId) {
157
169
  return daemonClient().getBrokerRecovery(nodeId, brokerExecutionId());
158
170
  }
159
171
  /** Node-scoped fault writes are for non-broker clients such as attach transport.
160
172
  * Broker-owned recovery mutations use the execution-fenced wrappers above. */
161
- export function recordNodeFault(nodeId, input) {
173
+ export async function recordNodeFault(nodeId, input) {
174
+ // `async` so a process with no way to reach the API (no node token) rejects instead of throwing inside a socket handler.
162
175
  return daemonClient().recordNodeFault(nodeId, input);
163
176
  }
164
- export function clearNodeFault(nodeId, opts = {}) {
177
+ export async function clearNodeFault(nodeId, opts = {}) {
165
178
  return daemonClient().clearNodeFault(nodeId, {
166
179
  ...(opts.link === undefined ? {} : { link: opts.link }),
167
180
  ...(opts.preserveEpisode === undefined ? {} : { preserve_episode: opts.preserveEpisode }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@north-light/api",
3
- "version": "0.3.575",
3
+ "version": "0.3.577",
4
4
  "description": "Typed crtrd /v1 API contract — DTOs, route builders, the error contract, the CrtrClient, and the command-plugin manifest format. Zero runtime dependencies.",
5
5
  "type": "module",
6
6
  "main": "./dist/api/index.js",