@byok-sdk/client 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -122,17 +122,19 @@ contents.
122
122
 
123
123
  ## Agent egress and explicit content reads
124
124
 
125
- `agentEgress` is consumed configuration, not a profile projection. The host
126
- must select one exact policy revision and an authenticated tenant id. Omitting
127
- contentful mode keeps runtime activity metadata/status-only; enabling it is an
128
- explicit product decision and requires the server capability. Reliable events
129
- are fsynced under the canonical Agent home and retire only after an exact ack.
125
+ `agentEgress` is consumed policy configuration, not a profile or tenant
126
+ projection. The host selects one exact policy revision. The daemon obtains its
127
+ tenant binding only from the authenticated pair response persisted in the
128
+ atomic local `DeviceRecord`; there is no `agentEgress.tenantId` setting and no
129
+ Profile/config, deviceId, or access-token fallback. Omitting contentful mode
130
+ keeps runtime activity metadata/status-only; enabling it is an explicit product
131
+ decision and requires the server capability. Reliable events are fsynced under
132
+ the canonical Agent home and retire only after an exact ack.
130
133
 
131
134
  ```ts
132
135
  createDaemon({
133
136
  // ...normal device, transport and agentHome configuration
134
137
  agentEgress: {
135
- tenantId: 'tenant-authority-from-salesko',
136
138
  policy: {
137
139
  policyRevision: 'salesko-agent-egress-r1',
138
140
  activity: { mode: 'metadata-status', delivery: 'latest-value' },
@@ -163,8 +165,9 @@ its local supplement. The local supplement can only narrow root, text, MIME,
163
165
  size and sensitive-name behavior; it cannot enable a wire-disabled surface.
164
166
  The SDK derives `agents/<agentId>`, `.byok/egress`, runtime-session evidence and
165
167
  the per-Agent content-read audit path. Salesko must not compose those paths.
166
- Tenant/device identity comes from authenticated host state; a request cannot
167
- override it. Transcript reads additionally require the exact persisted
168
+ Tenant/device identity comes from the persisted authenticated enrollment; a
169
+ request or editable host configuration cannot override it. Transcript reads
170
+ additionally require the exact persisted
168
171
  AgentRef/session/runtime/cwd handoff. Allowed content is uploaded through the
169
172
  authenticated blob channel. The content-free receipt is fsynced into the
170
173
  Agent-local reliable spool with stable event/cursor identity before send and
@@ -2,10 +2,10 @@
2
2
  import { randomUUID, createHash, randomBytes, timingSafeEqual, createHmac, createPrivateKey, generateKeyPairSync, sign } from 'crypto';
3
3
  import { readFileSync, promises, linkSync, fstatSync, lstatSync, unlinkSync, constants, readSync, openSync, writeFileSync, fchmodSync, fsyncSync, closeSync, opendirSync, existsSync, realpathSync, mkdirSync, renameSync, chmodSync, statSync, readdirSync } from 'fs';
4
4
  import path, { isAbsolute, join } from 'path';
5
- import { AGENT_CONTENT_ARTIFACT_READ_CAPABILITY, AGENT_CONTENT_TRANSCRIPT_READ_CAPABILITY, AGENT_CONTENT_WORKSPACE_READ_CAPABILITY, TASK_STATES, AgentEgressPolicySchema, CONFIGURED_TOOLSETS_MAX_ITEMS, ToolsetIdSchema, AgentRefSchema, AgentContentReceiptPayloadSchema, BYOK_PAIR_PATH, BYOK_CHALLENGE_PATH, BYOK_TOKEN_PATH, partitionAgentEvents, TASK_TRANSITIONS, encodeEnvelope, PROTOCOL_VERSION, createEnvelope, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY, byokBlobUrlPath, BYOK_BLOBS_PATH, byokBlobFinalizePath, AGENT_EGRESS_POLICY_CAPABILITY, parseMessage, checkResultDocument, MAX_MESSAGES_PER_BATCH, BYOK_CAPABILITIES_PATH, BYOK_PRESENCE_PATH, RuntimeIdSchema, TERMINAL_INFERENCE_USAGE_MAX_DURATION_MS, TERMINAL_INFERENCE_USAGE_MAX_TOKENS, RESULT_DOCUMENT_MAX_BYTES, decodeEnvelope, BYOK_EVENTS_PATH, BYOK_MESSAGES_PATH, MessagesSendResponseSchema, UnknownMessageTypeError, BYOK_WS_PATH } from '@byok-sdk/protocol';
5
+ import { AGENT_CONTENT_ARTIFACT_READ_CAPABILITY, AGENT_CONTENT_TRANSCRIPT_READ_CAPABILITY, AGENT_CONTENT_WORKSPACE_READ_CAPABILITY, TASK_STATES, AgentEgressPolicySchema, CONFIGURED_TOOLSETS_MAX_ITEMS, ToolsetIdSchema, AgentRefSchema, AgentContentReceiptPayloadSchema, BYOK_PAIR_PATH, PairResponseSchema, BYOK_CHALLENGE_PATH, BYOK_TOKEN_PATH, partitionAgentEvents, TASK_TRANSITIONS, encodeEnvelope, PROTOCOL_VERSION, createEnvelope, AGENT_EGRESS_RELIABLE_ACK_CAPABILITY, byokBlobUrlPath, BYOK_BLOBS_PATH, byokBlobFinalizePath, AGENT_EGRESS_POLICY_CAPABILITY, parseMessage, checkResultDocument, MAX_MESSAGES_PER_BATCH, BYOK_CAPABILITIES_PATH, BYOK_PRESENCE_PATH, RuntimeIdSchema, TERMINAL_INFERENCE_USAGE_MAX_DURATION_MS, TERMINAL_INFERENCE_USAGE_MAX_TOKENS, RESULT_DOCUMENT_MAX_BYTES, decodeEnvelope, BYOK_EVENTS_PATH, BYOK_MESSAGES_PATH, MessagesSendResponseSchema, UnknownMessageTypeError, BYOK_WS_PATH } from '@byok-sdk/protocol';
6
6
  import { execFile, spawn } from 'child_process';
7
7
  import os from 'os';
8
- import { DEVICE_ASSERTION_AUDIENCE_MAX_BYTES, DEVICE_ASSERTION_DEFAULT_TTL_MS, DEVICE_ASSERTION_MAX_TTL_MS, nonceSigningBytes, CapabilityDeclarationSchema, hasCapability, DeviceAssertionClaimsSchema, deviceAssertionSigningInput, DEVICE_ASSERTION_SCHEMA_ID } from '@byok-sdk/core';
8
+ import { isTenantId, DEVICE_ASSERTION_AUDIENCE_MAX_BYTES, DEVICE_ASSERTION_DEFAULT_TTL_MS, DEVICE_ASSERTION_MAX_TTL_MS, nonceSigningBytes, CapabilityDeclarationSchema, hasCapability, DeviceAssertionClaimsSchema, deviceAssertionSigningInput, DEVICE_ASSERTION_SCHEMA_ID } from '@byok-sdk/core';
9
9
  import { promisify } from 'util';
10
10
  import { fileURLToPath } from 'url';
11
11
  import 'readline';
@@ -4732,6 +4732,13 @@ var ApprovalRegistry = class {
4732
4732
  }
4733
4733
  };
4734
4734
  var MAX_DEVICE_RECORD_BYTES = 256 * 1024;
4735
+ var REPAIR_REQUIRED_MESSAGE = "device enrollment record is missing or has an invalid authenticated tenant binding; re-pair required";
4736
+ var DeviceRecordRePairRequiredError = class extends Error {
4737
+ constructor() {
4738
+ super(REPAIR_REQUIRED_MESSAGE);
4739
+ this.name = "DeviceRecordRePairRequiredError";
4740
+ }
4741
+ };
4735
4742
  function sameInode(left, right) {
4736
4743
  return left.dev === right.dev && left.ino === right.ino;
4737
4744
  }
@@ -4741,18 +4748,32 @@ function sameFileState(left, right) {
4741
4748
  function sameContentState(left, right) {
4742
4749
  return sameInode(left, right) && left.size === right.size && left.mtimeNs === right.mtimeNs;
4743
4750
  }
4751
+ function assertDeviceRecord(value) {
4752
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
4753
+ throw new DeviceRecordRePairRequiredError();
4754
+ }
4755
+ const parsed = value;
4756
+ if (typeof parsed.deviceId === "string" && isTenantId(parsed.tenantId) && typeof parsed.accessToken === "string" && typeof parsed.expiresAt === "string" && typeof parsed.devicePrivateKeyPem === "string" && typeof parsed.devicePublicKey === "string") {
4757
+ return;
4758
+ }
4759
+ throw new DeviceRecordRePairRequiredError();
4760
+ }
4744
4761
  function parseDeviceRecord(raw) {
4745
- const parsed = JSON.parse(raw);
4746
- if (typeof parsed.deviceId === "string" && typeof parsed.accessToken === "string" && typeof parsed.expiresAt === "string" && typeof parsed.devicePrivateKeyPem === "string" && typeof parsed.devicePublicKey === "string") {
4747
- return {
4748
- deviceId: parsed.deviceId,
4749
- accessToken: parsed.accessToken,
4750
- expiresAt: parsed.expiresAt,
4751
- devicePrivateKeyPem: parsed.devicePrivateKeyPem,
4752
- devicePublicKey: parsed.devicePublicKey
4753
- };
4762
+ let parsed;
4763
+ try {
4764
+ parsed = JSON.parse(raw);
4765
+ } catch {
4766
+ throw new DeviceRecordRePairRequiredError();
4754
4767
  }
4755
- return void 0;
4768
+ assertDeviceRecord(parsed);
4769
+ return {
4770
+ deviceId: parsed.deviceId,
4771
+ tenantId: parsed.tenantId,
4772
+ accessToken: parsed.accessToken,
4773
+ expiresAt: parsed.expiresAt,
4774
+ devicePrivateKeyPem: parsed.devicePrivateKeyPem,
4775
+ devicePublicKey: parsed.devicePublicKey
4776
+ };
4756
4777
  }
4757
4778
  var DeviceStore = class _DeviceStore {
4758
4779
  /**
@@ -4822,6 +4843,7 @@ var DeviceStore = class _DeviceStore {
4822
4843
  }
4823
4844
  }
4824
4845
  async save(record) {
4846
+ assertDeviceRecord(record);
4825
4847
  const storeDir = path.dirname(this.filePath);
4826
4848
  await ensureSecureDir(storeDir, this.secureDirOptions);
4827
4849
  await atomicWriteFile(this.filePath, JSON.stringify(record, null, 2), { mode: 384 });
@@ -4992,7 +5014,14 @@ var AuthManager = class {
4992
5014
  this.proactiveTimer = void 0;
4993
5015
  try {
4994
5016
  return await this.runCredentialMutation(async () => {
4995
- const existing = this.record ?? await this.opts.store.load();
5017
+ let existing = this.record;
5018
+ if (!existing) {
5019
+ try {
5020
+ existing = await this.opts.store.load();
5021
+ } catch (error) {
5022
+ if (!(error instanceof DeviceRecordRePairRequiredError)) throw error;
5023
+ }
5024
+ }
4996
5025
  const keyPair = existing ? { privateKey: importPrivateKeyPem(existing.devicePrivateKeyPem), publicKeyBase64Url: existing.devicePublicKey } : generateDeviceKeyPair();
4997
5026
  const url = new URL(BYOK_PAIR_PATH, toHttpBase(this.opts.serverUrl));
4998
5027
  const res = await fetch(url, {
@@ -5007,9 +5036,10 @@ var AuthManager = class {
5007
5036
  if (!res.ok) {
5008
5037
  throw new Error(`pairing failed: HTTP ${res.status} ${await safeErrorText(res)}`.trimEnd());
5009
5038
  }
5010
- const body = await res.json();
5039
+ const body = PairResponseSchema.parse(await res.json());
5011
5040
  const record = {
5012
5041
  deviceId: body.deviceId,
5042
+ tenantId: body.tenantId,
5013
5043
  accessToken: body.accessToken,
5014
5044
  expiresAt: resolvePairExpiry(body.refreshHint),
5015
5045
  devicePrivateKeyPem: exportPrivateKeyPem(keyPair.privateKey),
@@ -12428,9 +12458,14 @@ var AgentEgressController = class {
12428
12458
  latestStatus = emptyLane();
12429
12459
  reliableStatus = emptyLane();
12430
12460
  drops = [];
12461
+ active = true;
12431
12462
  get policy() {
12432
12463
  return this.options.policy;
12433
12464
  }
12465
+ /** Permanently fail closed after its authenticated enrollment is replaced. */
12466
+ deactivate() {
12467
+ this.active = false;
12468
+ }
12434
12469
  status() {
12435
12470
  const reliable = this.reliableRecords();
12436
12471
  return Object.freeze({
@@ -12448,6 +12483,10 @@ var AgentEgressController = class {
12448
12483
  /** Project before TaskRunner builds a `task.progress` envelope. */
12449
12484
  projectLatestValue(input) {
12450
12485
  if (input.agentRef === void 0) return Object.freeze([...input.events]);
12486
+ if (!this.active) {
12487
+ this.noteDrop("latest-value", "policy_denied", input.agentRef);
12488
+ return [];
12489
+ }
12451
12490
  if (this.options.policy.activity.mode === "contentful-trajectory" && !input.serverCapabilities.includes("agent-egress-policy")) {
12452
12491
  this.noteDrop("latest-value", "capability_missing", input.agentRef);
12453
12492
  return [];
@@ -12470,7 +12509,7 @@ var AgentEgressController = class {
12470
12509
  return latest === void 0 ? [] : Object.freeze([latest]);
12471
12510
  }
12472
12511
  async appendReliable(input) {
12473
- if (this.options.tenantId === void 0) {
12512
+ if (!this.active || this.options.tenantId === void 0) {
12474
12513
  this.noteDrop("reliable", "policy_denied", input.agentRef);
12475
12514
  return { ok: false, reason: "policy_denied" };
12476
12515
  }
@@ -12508,7 +12547,7 @@ var AgentEgressController = class {
12508
12547
  * with `wireType: agent.content.receipt` before any transport attempt.
12509
12548
  */
12510
12549
  async appendContentReceipt(input) {
12511
- if (this.options.tenantId === void 0) {
12550
+ if (!this.active || this.options.tenantId === void 0) {
12512
12551
  this.noteDrop("reliable", "policy_denied", input.agentRef);
12513
12552
  return { ok: false, reason: "policy_denied" };
12514
12553
  }
@@ -12532,7 +12571,7 @@ var AgentEgressController = class {
12532
12571
  /** Retires only the record whose full Agent/tenant/revision/id/cursor tuple matches. */
12533
12572
  async acknowledge(ack) {
12534
12573
  const spool = this.spools.get(agentKey(ack.agentRef));
12535
- if (!spool || this.options.tenantId === void 0 || ack.tenantId !== this.options.tenantId) {
12574
+ if (!this.active || !spool || this.options.tenantId === void 0 || ack.tenantId !== this.options.tenantId) {
12536
12575
  this.noteDrop("reliable", "ack_mismatch", ack.agentRef);
12537
12576
  return false;
12538
12577
  }
@@ -12543,6 +12582,7 @@ var AgentEgressController = class {
12543
12582
  /** Re-open every existing Agent-local spool before retrying stable records after restart. */
12544
12583
  async recover(agentsRoot) {
12545
12584
  if (!path.isAbsolute(agentsRoot)) throw new Error("Agent egress recovery root must be absolute");
12585
+ if (!this.active) throw new Error("Agent egress recovery requires an active authenticated enrollment");
12546
12586
  if (this.options.tenantId === void 0) {
12547
12587
  throw new Error("Agent egress recovery requires one authenticated tenant authority");
12548
12588
  }
@@ -13650,9 +13690,6 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
13650
13690
  if (config.agentEgress !== void 0 && config.agentHome === void 0) {
13651
13691
  throw new Error("DaemonConfig.agentEgress requires DaemonConfig.agentHome for the per-Agent local spool");
13652
13692
  }
13653
- if (config.agentEgress !== void 0 && (config.agentEgress.tenantId.length === 0 || config.agentEgress.tenantId.trim() !== config.agentEgress.tenantId)) {
13654
- throw new Error("DaemonConfig.agentEgress.tenantId must be a non-empty canonical authenticated tenant id");
13655
- }
13656
13693
  const egressPolicy = resolveAgentEgressPolicy(config.agentEgress?.policy);
13657
13694
  const egressBatcherOptions = egressPolicy.activity.mode === "contentful-trajectory" ? {
13658
13695
  ...config.progressBatch,
@@ -13683,9 +13720,8 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
13683
13720
  });
13684
13721
  const agentSessionHandoffs = config.agentHome === void 0 ? void 0 : new AgentSessionHandoffStore();
13685
13722
  const agentContentReadPolicies = resolveContentReadPolicies(egressPolicy, config.agentEgress?.contentRead);
13686
- const agentEgress = new AgentEgressController({
13723
+ let agentEgress = new AgentEgressController({
13687
13724
  policy: egressPolicy,
13688
- ...config.agentEgress?.tenantId === void 0 ? {} : { tenantId: config.agentEgress.tenantId },
13689
13725
  ...config.agentEgress?.sanitizer === void 0 ? {} : { sanitizer: config.agentEgress.sanitizer }
13690
13726
  });
13691
13727
  const gitWorkspaceManager = config.gitWorkspace ? overrides.gitWorkspace?.manager ?? new GitWorkspaceManager(config.workspaceRoot, { ownerId: stableGitWorkspaceOwnerId(storeDir, config.productId) }) : void 0;
@@ -13695,11 +13731,6 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
13695
13731
  if (config.hostedJournal.mode !== "sqlite") {
13696
13732
  throw new Error(`DaemonConfig.hostedJournal.mode must be "sqlite" \u2014 got ${JSON.stringify(config.hostedJournal.mode)}`);
13697
13733
  }
13698
- if (typeof config.hostedJournal.tenantId !== "string" || config.hostedJournal.tenantId.trim() === "") {
13699
- throw new Error(
13700
- "DaemonConfig.hostedJournal.tenantId must be a non-empty tenant id \u2014 a hosted journal row with no tenant is durable evidence nobody can act on"
13701
- );
13702
- }
13703
13734
  if (config.hostedJournal.storagePolicy) {
13704
13735
  resolvedStoragePolicy = resolveLocalStoragePolicy(config.hostedJournal.storagePolicy);
13705
13736
  }
@@ -13768,6 +13799,8 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
13768
13799
  const approvalRegistry = new ApprovalRegistry();
13769
13800
  let connection;
13770
13801
  let connectionState = "closed";
13802
+ let daemonStarted = false;
13803
+ let tenantRebinding = false;
13771
13804
  let runner;
13772
13805
  let controlServerHandle;
13773
13806
  let daemonOwnerLease;
@@ -13820,11 +13853,31 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
13820
13853
  return runLifecycleMutation(() => pairUnderLease(pairingCode));
13821
13854
  }
13822
13855
  async function pairUnderLease(pairingCode) {
13856
+ const wasRunning = daemonStarted;
13857
+ if (wasRunning) tenantRebinding = true;
13823
13858
  const acquiredHere = daemonOwnerLease === void 0;
13824
13859
  if (acquiredHere) daemonOwnerLease = await acquireDaemonOwner(storeDir, "daemon");
13860
+ let replacementPersisted = false;
13825
13861
  try {
13826
- const previous = await store.load();
13862
+ let previous;
13863
+ try {
13864
+ previous = await store.load();
13865
+ } catch (error) {
13866
+ if (!(error instanceof DeviceRecordRePairRequiredError)) throw error;
13867
+ }
13827
13868
  const record = await auth.pair(pairingCode);
13869
+ replacementPersisted = true;
13870
+ if (config.agentEgress !== void 0) {
13871
+ agentEgress.deactivate();
13872
+ agentEgress = new AgentEgressController({
13873
+ policy: egressPolicy,
13874
+ ...config.agentEgress.sanitizer === void 0 ? {} : { sanitizer: config.agentEgress.sanitizer }
13875
+ });
13876
+ }
13877
+ if (wasRunning) {
13878
+ await runShutdownSequence("re-pairing enrollment binding");
13879
+ tenantRebinding = false;
13880
+ }
13828
13881
  if (previous && previous.deviceId !== record.deviceId) {
13829
13882
  await cursorStore.clear(config.serverUrl, previous.deviceId);
13830
13883
  }
@@ -13835,6 +13888,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
13835
13888
  }
13836
13889
  return record;
13837
13890
  } catch (err) {
13891
+ if (wasRunning && !replacementPersisted) tenantRebinding = false;
13838
13892
  if (acquiredHere) {
13839
13893
  await daemonOwnerLease?.release();
13840
13894
  daemonOwnerLease = void 0;
@@ -13857,6 +13911,13 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
13857
13911
  if (!record) {
13858
13912
  throw new Error("device is not paired yet; call pair(pairingCode) first");
13859
13913
  }
13914
+ if (config.agentEgress !== void 0) {
13915
+ agentEgress = new AgentEgressController({
13916
+ policy: egressPolicy,
13917
+ tenantId: record.tenantId,
13918
+ ...config.agentEgress.sanitizer === void 0 ? {} : { sanitizer: config.agentEgress.sanitizer }
13919
+ });
13920
+ }
13860
13921
  await agentHomeManager?.preflight();
13861
13922
  if (config.agentEgress !== void 0 && config.agentHome !== void 0) {
13862
13923
  await agentEgress.recover(path.join(config.agentHome.hostStorageRoot, "agents"));
@@ -13917,7 +13978,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
13917
13978
  config.agentEgress !== void 0,
13918
13979
  agentContentReadPolicies
13919
13980
  );
13920
- const journalIdentity = config.hostedJournal ? { tenantId: config.hostedJournal.tenantId, productId: config.productId, deviceId: record.deviceId } : void 0;
13981
+ const journalIdentity = config.hostedJournal ? { tenantId: record.tenantId, productId: config.productId, deviceId: record.deviceId } : void 0;
13921
13982
  const sendSanitizedEnvelope = activeJournal && journalIdentity ? (envelope) => {
13922
13983
  observer.handleOutboundEnvelope(envelope);
13923
13984
  const terminalKind = terminalKindOf(envelope.type);
@@ -14055,9 +14116,8 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
14055
14116
  runner = new TaskRunner(deps);
14056
14117
  const handleAgentEgressEnvelope = async (envelope) => {
14057
14118
  if (envelope.type !== "agent.egress.ack") return false;
14058
- const tenantId = config.agentEgress?.tenantId;
14059
- if (tenantId === void 0) return true;
14060
- await agentEgress.acknowledge({ ...envelope.payload, tenantId });
14119
+ if (config.agentEgress === void 0) return true;
14120
+ await agentEgress.acknowledge({ ...envelope.payload, tenantId: record.tenantId });
14061
14121
  return true;
14062
14122
  };
14063
14123
  const handleAgentContentReadEnvelope = async (envelope) => {
@@ -14109,7 +14169,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
14109
14169
  const result = await policyEngine.read({
14110
14170
  requestId: payload.requestId,
14111
14171
  actor: payload.actor,
14112
- tenantId: config.agentEgress.tenantId,
14172
+ tenantId: record.tenantId,
14113
14173
  deviceId: record.deviceId,
14114
14174
  agentRef: payload.agentRef,
14115
14175
  surface: payload.surface,
@@ -14205,6 +14265,9 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
14205
14265
  //
14206
14266
  // The no-journal branch is the ORIGINAL closure, unchanged.
14207
14267
  onEnvelope: activeJournal && journalIdentity ? async (envelope) => {
14268
+ if (tenantRebinding) {
14269
+ throw new Error("tenant enrollment is being re-paired; inbound work is blocked until restart");
14270
+ }
14208
14271
  observer.handleInboundEnvelope(envelope);
14209
14272
  if (await handleAgentEgressEnvelope(envelope)) return;
14210
14273
  if (await handleAgentContentReadEnvelope(envelope)) return;
@@ -14212,6 +14275,9 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
14212
14275
  await activeJournal.appendEnvelope(toJournalEnvelopeRecord(envelope, journalIdentity));
14213
14276
  return runner?.handleEnvelope(envelope) ?? Promise.resolve();
14214
14277
  } : (envelope) => {
14278
+ if (tenantRebinding) {
14279
+ return Promise.reject(new Error("tenant enrollment is being re-paired; inbound work is blocked until restart"));
14280
+ }
14215
14281
  observer.handleInboundEnvelope(envelope);
14216
14282
  if (envelope.type === "agent.egress.ack") return handleAgentEgressEnvelope(envelope).then(() => void 0);
14217
14283
  if (envelope.type === "agent.content.read") return handleAgentContentReadEnvelope(envelope).then(() => void 0);
@@ -14243,6 +14309,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
14243
14309
  dispatchReliableRecord(record2);
14244
14310
  }
14245
14311
  startPresenceProducer();
14312
+ daemonStarted = true;
14246
14313
  } catch (err) {
14247
14314
  try {
14248
14315
  await runShutdownSequence("startup failed", { drainTimeoutMs: 0 });
@@ -14387,6 +14454,7 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
14387
14454
  if (!mutationBarrierComplete) {
14388
14455
  throw new Error("daemon shutdown mutation barrier is incomplete; ownership lease retained");
14389
14456
  }
14457
+ daemonStarted = false;
14390
14458
  }
14391
14459
  async function stop(opts = {}) {
14392
14460
  shuttingDown = true;
@@ -14708,6 +14776,9 @@ function buildDaemonWithAdapters(config, adapters, overrides = {}, assertionProb
14708
14776
  if (config.agentEgress === void 0 || agentHomeManager === void 0) {
14709
14777
  throw new Error("Agent reliable egress is not configured");
14710
14778
  }
14779
+ if (tenantRebinding) {
14780
+ throw new Error("tenant enrollment is being re-paired; reliable egress is blocked until restart");
14781
+ }
14711
14782
  const binding = await agentHomeManager.acquire(input.agentRef);
14712
14783
  try {
14713
14784
  await agentHomeManager.initialize(binding);
@@ -15381,7 +15452,7 @@ function createServiceLifecycle(def, opts = {}) {
15381
15452
 
15382
15453
  // src/bin/official-release.ts
15383
15454
  var OFFICIAL_LOCAL_AGENT_RELEASE = resolveLocalAgentReleaseIdentity({
15384
- version: "0.6.1"
15455
+ version: "0.7.0"
15385
15456
  });
15386
15457
 
15387
15458
  // src/bin/config.ts