@camstack/system 1.2.109 → 1.2.111

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.
Files changed (61) hide show
  1. package/dist/addon-runner.js +1 -1
  2. package/dist/addon-runner.mjs +1 -1
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/alerts/alerts.addon.js +1 -1
  8. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  9. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  11. package/dist/builtins/console-logging/index.js +1 -1
  12. package/dist/builtins/console-logging/index.mjs +1 -1
  13. package/dist/builtins/core-blocks/core-blocks.addon.js +1 -1
  14. package/dist/builtins/core-blocks/core-blocks.addon.mjs +1 -1
  15. package/dist/builtins/device-manager/device-manager.addon.js +2 -2
  16. package/dist/builtins/device-manager/device-manager.addon.mjs +2 -2
  17. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  18. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  19. package/dist/builtins/hub-forwarder/index.js +1 -1
  20. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  21. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  22. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  23. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  24. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  25. package/dist/builtins/local-network/local-network.addon.js +1 -1
  26. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  27. package/dist/builtins/loki-logging/index.js +1 -1
  28. package/dist/builtins/loki-logging/index.mjs +1 -1
  29. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  30. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  31. package/dist/builtins/platform-probe/index.js +1 -1
  32. package/dist/builtins/platform-probe/index.mjs +1 -1
  33. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  34. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  35. package/dist/builtins/snapshot/index.js +1 -1
  36. package/dist/builtins/snapshot/index.mjs +1 -1
  37. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  38. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  39. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +2 -2
  40. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +2 -2
  41. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  42. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  43. package/dist/builtins/system-config/system-config.addon.js +1 -1
  44. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  45. package/dist/builtins/winston-logging/index.js +1 -1
  46. package/dist/builtins/winston-logging/index.mjs +1 -1
  47. package/dist/{dist-Ctfhlrtl.js → dist-CQkr5Kw0.js} +31 -51
  48. package/dist/{dist-_oC_QkQA.mjs → dist-CW4Cims4.mjs} +31 -51
  49. package/dist/index.js +87 -9
  50. package/dist/index.mjs +87 -9
  51. package/dist/kernel/capability-registry.d.ts +51 -3
  52. package/dist/kernel/host-externals.d.ts +1 -1
  53. package/dist/kernel/index.d.ts +1 -1
  54. package/dist/kernel/lifecycle/staging-area.d.ts +1 -0
  55. package/dist/kernel/transport/child-cap-protocol.d.ts +12 -0
  56. package/dist/kernel/transport/local-child-registry.d.ts +9 -2
  57. package/dist/{manifest-python-deps-5ei2xVUh.mjs → manifest-python-deps-Cb1ANt3i.mjs} +35 -8
  58. package/dist/{manifest-python-deps-GejnH--L.js → manifest-python-deps-MA_BZGBz.js} +35 -8
  59. package/dist/{retired-settings-keys-pNmoHPTg.js → retired-settings-keys-DCESas5O.js} +1 -1
  60. package/dist/{retired-settings-keys-Bsjf7HQ-.mjs → retired-settings-keys-DGgOxOir.mjs} +1 -1
  61. package/package.json +1 -1
@@ -11653,128 +11653,103 @@ var COCO_TO_MACRO = {
11653
11653
  var AUDIO_MACRO_LABELS = [
11654
11654
  {
11655
11655
  id: "speech",
11656
- name: "Speech",
11657
- icon: "🗣️"
11656
+ name: "Speech"
11658
11657
  },
11659
11658
  {
11660
11659
  id: "scream",
11661
- name: "Scream / Shout",
11662
- icon: "😱"
11660
+ name: "Scream / Shout"
11663
11661
  },
11664
11662
  {
11665
11663
  id: "crying",
11666
- name: "Crying / Baby",
11667
- icon: "😢"
11664
+ name: "Crying / Baby"
11668
11665
  },
11669
11666
  {
11670
11667
  id: "laughter",
11671
- name: "Laughter",
11672
- icon: "😂"
11668
+ name: "Laughter"
11673
11669
  },
11674
11670
  {
11675
11671
  id: "music",
11676
- name: "Music",
11677
- icon: "🎵"
11672
+ name: "Music"
11678
11673
  },
11679
11674
  {
11680
11675
  id: "dog",
11681
- name: "Dog",
11682
- icon: "🐕"
11676
+ name: "Dog"
11683
11677
  },
11684
11678
  {
11685
11679
  id: "cat",
11686
- name: "Cat",
11687
- icon: "🐈"
11680
+ name: "Cat"
11688
11681
  },
11689
11682
  {
11690
11683
  id: "bird",
11691
- name: "Bird",
11692
- icon: "🐦"
11684
+ name: "Bird"
11693
11685
  },
11694
11686
  {
11695
11687
  id: "animal",
11696
- name: "Animal (other)",
11697
- icon: "🐾"
11688
+ name: "Animal (other)"
11698
11689
  },
11699
11690
  {
11700
11691
  id: "alarm",
11701
- name: "Alarm / Siren",
11702
- icon: "🚨"
11692
+ name: "Alarm / Siren"
11703
11693
  },
11704
11694
  {
11705
11695
  id: "doorbell",
11706
- name: "Doorbell / Knock",
11707
- icon: "🔔"
11696
+ name: "Doorbell / Knock"
11708
11697
  },
11709
11698
  {
11710
11699
  id: "glass_breaking",
11711
- name: "Glass Breaking",
11712
- icon: "💥"
11700
+ name: "Glass Breaking"
11713
11701
  },
11714
11702
  {
11715
11703
  id: "gunshot",
11716
- name: "Gunshot / Explosion",
11717
- icon: "💣"
11704
+ name: "Gunshot / Explosion"
11718
11705
  },
11719
11706
  {
11720
11707
  id: "vehicle",
11721
- name: "Vehicle",
11722
- icon: "🚗"
11708
+ name: "Vehicle"
11723
11709
  },
11724
11710
  {
11725
11711
  id: "siren",
11726
- name: "Emergency Siren",
11727
- icon: "🚑"
11712
+ name: "Emergency Siren"
11728
11713
  },
11729
11714
  {
11730
11715
  id: "fire",
11731
- name: "Fire / Smoke",
11732
- icon: "🔥"
11716
+ name: "Fire / Smoke"
11733
11717
  },
11734
11718
  {
11735
11719
  id: "water",
11736
- name: "Water",
11737
- icon: "💧"
11720
+ name: "Water"
11738
11721
  },
11739
11722
  {
11740
11723
  id: "wind",
11741
- name: "Wind / Weather",
11742
- icon: "🌬️"
11724
+ name: "Wind / Weather"
11743
11725
  },
11744
11726
  {
11745
11727
  id: "door",
11746
- name: "Door",
11747
- icon: "🚪"
11728
+ name: "Door"
11748
11729
  },
11749
11730
  {
11750
11731
  id: "footsteps",
11751
- name: "Footsteps",
11752
- icon: "👣"
11732
+ name: "Footsteps"
11753
11733
  },
11754
11734
  {
11755
11735
  id: "crowd",
11756
- name: "Crowd / Chatter",
11757
- icon: "👥"
11736
+ name: "Crowd / Chatter"
11758
11737
  },
11759
11738
  {
11760
11739
  id: "telephone",
11761
- name: "Telephone",
11762
- icon: "📞"
11740
+ name: "Telephone"
11763
11741
  },
11764
11742
  {
11765
11743
  id: "engine",
11766
- name: "Engine / Motor",
11767
- icon: "⚙️"
11744
+ name: "Engine / Motor"
11768
11745
  },
11769
11746
  {
11770
11747
  id: "tools",
11771
- name: "Tools / Construction",
11772
- icon: "🔨"
11748
+ name: "Tools / Construction"
11773
11749
  },
11774
11750
  {
11775
11751
  id: "silence",
11776
- name: "Silence",
11777
- icon: "🤫"
11752
+ name: "Silence"
11778
11753
  }
11779
11754
  ];
11780
11755
  var YAMNET_TO_MACRO = {
@@ -16634,6 +16609,12 @@ var DetailResultSchema = z.object({
16634
16609
  /** FRAME-space bbox (already mapped back from crop space). */
16635
16610
  bbox: NativeCropBboxSchema.optional(),
16636
16611
  embedding: z.string().optional(),
16612
+ /**
16613
+ * L2 magnitude of the RAW pre-normalization embedding. Additive and optional
16614
+ * so an older runner that does not send it degrades to "unmeasurable", which
16615
+ * the consuming gate treats as accept — never as reject.
16616
+ */
16617
+ embeddingMagnitude: z.number().optional(),
16637
16618
  label: z.string().optional(),
16638
16619
  /**
16639
16620
  * The tier `label` occupies, copied VERBATIM from the producing step's
@@ -37784,7 +37765,6 @@ var NC_AUDIO_DEFAULTS = {
37784
37765
  hitPercent: 60,
37785
37766
  samplingSeconds: 10
37786
37767
  };
37787
- new Map(AUDIO_MACRO_LABELS.flatMap((macro) => macro.icon === void 0 ? [] : [[macro.id, macro.icon]]));
37788
37768
  new Set(["devices", "classes"]);
37789
37769
  NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
37790
37770
  /**
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("./chunk-Cek0wNdY.js");
3
- const require_dist = require("./dist-Ctfhlrtl.js");
3
+ const require_dist = require("./dist-CQkr5Kw0.js");
4
4
  const require_builtins_alerts_alerts_addon = require("./builtins/alerts/alerts.addon.js");
5
5
  require("./builtins/alerts/index.js");
6
6
  const require_formatter = require("./formatter-DqAKDlvN.js");
@@ -25,7 +25,7 @@ const require_builtins_system_config_system_config_addon = require("./builtins/s
25
25
  require("./builtins/system-config/index.js");
26
26
  const require_builtins_winston_logging_index = require("./builtins/winston-logging/index.js");
27
27
  const require_file_data_plane = require("./file-data-plane-DUHPHa-Y.js");
28
- const require_manifest_python_deps = require("./manifest-python-deps-GejnH--L.js");
28
+ const require_manifest_python_deps = require("./manifest-python-deps-MA_BZGBz.js");
29
29
  const require_resource_monitor = require("./resource-monitor-CdnzxBLP.js");
30
30
  const require_custom_action_registry = require("./custom-action-registry-jY0NOZK8.js");
31
31
  let zod = require("zod");
@@ -5367,6 +5367,7 @@ var CapabilityRegistry = class CapabilityRegistry {
5367
5367
  const state = {
5368
5368
  definition,
5369
5369
  providers: /* @__PURE__ */ new Map(),
5370
+ providerOwners: /* @__PURE__ */ new Map(),
5370
5371
  activeAddonId: null,
5371
5372
  router: null,
5372
5373
  disabledProviders: /* @__PURE__ */ new Set()
@@ -5424,8 +5425,15 @@ var CapabilityRegistry = class CapabilityRegistry {
5424
5425
  * Double-register of the same pair is always a programmer error —
5425
5426
  * two legitimately different addons implementing the same cap keep
5426
5427
  * using different `addonId`s and the user picks one via `configReader`.
5427
- */
5428
- registerProvider(capabilityName, addonId, provider) {
5428
+ *
5429
+ * `owner` (optional) stamps the registration with the incarnation that made
5430
+ * it — see {@link ProviderOwnerToken}. Once stamped, only an unregister
5431
+ * presenting the SAME token can delete it. Registration paths whose key is
5432
+ * reused across process generations (the UDS child manifest, the D3
5433
+ * `registerNode` handshake) MUST pass one; ownerless registrations keep the
5434
+ * historical unconditional behaviour.
5435
+ */
5436
+ registerProvider(capabilityName, addonId, provider, owner) {
5429
5437
  let state = this.capabilities.get(capabilityName);
5430
5438
  if (!state) {
5431
5439
  const colonIdx = capabilityName.indexOf(":");
@@ -5457,6 +5465,7 @@ var CapabilityRegistry = class CapabilityRegistry {
5457
5465
  if (missing.length > 0) throw new Error(`CapabilityRegistry: provider "${addonId}" for capability "${capabilityName}" declares exposesDeviceSettings: true but is missing DeviceSettingsContribution method(s): ${missing.join(", ")}. Implement them on the provider (or drop the flag from the cap def).`);
5458
5466
  }
5459
5467
  state.providers.set(addonId, provider);
5468
+ if (owner !== void 0) state.providerOwners.set(addonId, owner);
5460
5469
  if (state.definition.mode === "singleton") {
5461
5470
  const userChoice = this.configReader?.(capabilityName);
5462
5471
  const preferred = state.definition.preferredProvider;
@@ -5487,7 +5496,8 @@ var CapabilityRegistry = class CapabilityRegistry {
5487
5496
  tags: { addonId },
5488
5497
  meta: {
5489
5498
  capability: capabilityName,
5490
- mode: state.definition.mode
5499
+ mode: state.definition.mode,
5500
+ ...owner === void 0 ? {} : { owner }
5491
5501
  }
5492
5502
  });
5493
5503
  this.emitEvent("capability:provider-registered", {
@@ -5534,11 +5544,70 @@ var CapabilityRegistry = class CapabilityRegistry {
5534
5544
  const declarers = this.manifestDeclarers.get(capabilityName);
5535
5545
  return declarers ? [...declarers] : [];
5536
5546
  }
5537
- /** Unregister a provider. For singleton: clears active. For collection: removes from list. */
5538
- unregisterProvider(capabilityName, addonId) {
5547
+ /**
5548
+ * The owner token a registration declared, or `null` when the pair is not
5549
+ * registered or was registered without one.
5550
+ */
5551
+ getProviderOwner(capabilityName, addonId) {
5552
+ return this.capabilities.get(capabilityName)?.providerOwners.get(addonId) ?? null;
5553
+ }
5554
+ /**
5555
+ * Hand an EXISTING registration to a new owner, without touching the provider
5556
+ * instance, the active-singleton selection or any event.
5557
+ *
5558
+ * The takeover case: a replacement runner handshakes with an UNCHANGED cap
5559
+ * set, so the manifest diff correctly leaves the registration alone — but the
5560
+ * registration is still stamped with the dead generation's token, and nothing
5561
+ * the LIVE generation can present would ever match it. Re-stamping keeps the
5562
+ * capability cleanable when the successor eventually dies.
5563
+ *
5564
+ * Returns `false` when the pair is not registered (nothing to hand over) —
5565
+ * the caller decides whether that is a register-instead or a diagnosis.
5566
+ */
5567
+ reassignProviderOwner(capabilityName, addonId, owner) {
5568
+ const state = this.capabilities.get(capabilityName);
5569
+ if (!state || !state.providers.has(addonId)) return false;
5570
+ const previous = state.providerOwners.get(addonId);
5571
+ if (previous === owner) return true;
5572
+ state.providerOwners.set(addonId, owner);
5573
+ this.logger.info("Capability registration ownership reassigned", {
5574
+ tags: { addonId },
5575
+ meta: {
5576
+ capability: capabilityName,
5577
+ owner,
5578
+ previousOwner: previous ?? null
5579
+ }
5580
+ });
5581
+ return true;
5582
+ }
5583
+ /**
5584
+ * Unregister a provider. For singleton: clears active. For collection:
5585
+ * removes from list.
5586
+ *
5587
+ * `owner` is the caller's PROOF that it owns the registration it is deleting.
5588
+ * When the registration declared an owner (see {@link ProviderOwnerToken})
5589
+ * and the caller presents a different one — or none at all — this is a
5590
+ * logged no-op: the caller belongs to a generation that no longer holds the
5591
+ * key. This is what stops a dead runner's teardown from erasing its
5592
+ * successor's live registration ([D188](../../../../docs/decisions/adr-0188-an-unregister-carries-proof-of-ownership.md)).
5593
+ */
5594
+ unregisterProvider(capabilityName, addonId, owner) {
5539
5595
  const state = this.capabilities.get(capabilityName);
5540
5596
  if (!state) return;
5597
+ const registeredOwner = state.providerOwners.get(addonId);
5598
+ if (registeredOwner !== void 0 && registeredOwner !== owner) {
5599
+ this.logger.info("Unregister ignored — registration is owned by another incarnation", {
5600
+ tags: { addonId },
5601
+ meta: {
5602
+ capability: capabilityName,
5603
+ registeredOwner,
5604
+ presentedOwner: owner ?? null
5605
+ }
5606
+ });
5607
+ return;
5608
+ }
5541
5609
  state.providers.delete(addonId);
5610
+ state.providerOwners.delete(addonId);
5542
5611
  if (state.definition.mode === "singleton" && state.activeAddonId === addonId) {
5543
5612
  const userChoice = this.configReader?.(capabilityName);
5544
5613
  const preferred = state.definition.preferredProvider;
@@ -94555,6 +94624,11 @@ var JobJournal = class {
94555
94624
  function safeSegment(name) {
94556
94625
  return name.replace(/[^a-zA-Z0-9._-]/g, "_");
94557
94626
  }
94627
+ /** An exact semver — the only shape the staged package.json can be compared
94628
+ * against literally. Anything else ('latest', 'beta', a range) is a dist-tag
94629
+ * the FETCHER resolves; comparing the resolved version to the literal tag
94630
+ * failed every tag-based update (30/30 on 2026-08-20). */
94631
+ var EXACT_SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
94558
94632
  var StagingArea = class {
94559
94633
  stagingDir;
94560
94634
  deps;
@@ -94572,8 +94646,12 @@ var StagingArea = class {
94572
94646
  if (input.signal.aborted) throw new Error("staging aborted after fetch");
94573
94647
  await this.deps.extract(tgz, destDir);
94574
94648
  const raw = JSON.parse(node_fs.readFileSync(node_path.join(destDir, "package.json"), "utf-8"));
94575
- if (raw.version !== input.version) throw new Error(`staging validation failed: ${input.name} expected version ${input.version}, got ${String(raw.version)}`);
94576
- return { stagedPath: destDir };
94649
+ if (typeof raw.version !== "string" || raw.version.length === 0) throw new Error(`staging validation failed: ${input.name} staged a package.json with no version`);
94650
+ if (EXACT_SEMVER.test(input.version) && raw.version !== input.version) throw new Error(`staging validation failed: ${input.name} expected version ${input.version}, got ${raw.version}`);
94651
+ return {
94652
+ stagedPath: destDir,
94653
+ resolvedVersion: raw.version
94654
+ };
94577
94655
  }
94578
94656
  cleanup(jobId) {
94579
94657
  node_fs.rmSync(node_path.join(this.stagingDir, jobId), {
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { a as __toCommonJS, i as __require, n as __esmMin, o as __toESM$1, r as __exportAll, t as __commonJSMin$1 } from "./chunk-CNf5ZN-e.mjs";
2
- import { At as asNumber, B as extractNestedAddonId, Bt as parseJsonUnknown$1, Dt as ReadinessTimeoutError, Et as ReadinessRegistry, Ft as expandCapMethods, Gt as EventCategory$1, H as isArrayOutputSchema, Ht as resolveCapMount, J as lifecycleJobSchema, K as isVoidInput, Mt as createEvent, Nt as emitDownForOwnedCaps, Q as looseSchema, St as DEVICE_STATUS_METHOD, U as isCollectionArrayMethod, Ut as scopeKey, Vt as readinessKey, W as isObjectInput, Z as logLevelAtMost, bt as DATAPLANE_SECRET_HEADER$1, d as METHOD_ACCESS_MAP, f as RUNTIME_DEFAULTS, it as procedureAuthKey, jt as asString$1, kt as asJsonObject$1, ot as scopesAllowAddon, q as kebabToCamel, st as scopesAllowDeviceCap, t as ALL_CAPABILITY_DEFINITIONS, tt as objectInputDeclaresAddonId, vt as errMsg$1, xt as DEVICE_SETTINGS_CONTRIBUTION_METHODS, y as addonSettingsCapability, zt as parseJsonObject } from "./dist-_oC_QkQA.mjs";
2
+ import { At as asNumber, B as extractNestedAddonId, Bt as parseJsonUnknown$1, Dt as ReadinessTimeoutError, Et as ReadinessRegistry, Ft as expandCapMethods, Gt as EventCategory$1, H as isArrayOutputSchema, Ht as resolveCapMount, J as lifecycleJobSchema, K as isVoidInput, Mt as createEvent, Nt as emitDownForOwnedCaps, Q as looseSchema, St as DEVICE_STATUS_METHOD, U as isCollectionArrayMethod, Ut as scopeKey, Vt as readinessKey, W as isObjectInput, Z as logLevelAtMost, bt as DATAPLANE_SECRET_HEADER$1, d as METHOD_ACCESS_MAP, f as RUNTIME_DEFAULTS, it as procedureAuthKey, jt as asString$1, kt as asJsonObject$1, ot as scopesAllowAddon, q as kebabToCamel, st as scopesAllowDeviceCap, t as ALL_CAPABILITY_DEFINITIONS, tt as objectInputDeclaresAddonId, vt as errMsg$1, xt as DEVICE_SETTINGS_CONTRIBUTION_METHODS, y as addonSettingsCapability, zt as parseJsonObject } from "./dist-CW4Cims4.mjs";
3
3
  import { AlertCenterAddon } from "./builtins/alerts/alerts.addon.mjs";
4
4
  import "./builtins/alerts/index.mjs";
5
5
  import { t as formatLogLine } from "./formatter-B7qW8bPJ.mjs";
@@ -24,7 +24,7 @@ import { SystemConfigAddon } from "./builtins/system-config/system-config.addon.
24
24
  import "./builtins/system-config/index.mjs";
25
25
  import { WinstonDestination, WinstonLoggingAddon } from "./builtins/winston-logging/index.mjs";
26
26
  import { a as parseTokenizedUrl, c as collectModelFiles, d as downloadModel, f as ensureModel, h as isModelDownloaded, i as parseRangeHeader, l as deleteModelFromDisk, m as getModelFilePath, n as contentTypeFor, o as resolveFilePath, p as fetchJson, r as createAuthenticatedFileServer, s as ModelDownloadService, t as createFileDataPlaneHandler, u as downloadFile } from "./file-data-plane-CuE_hBli.mjs";
27
- import { $ as buildNativeCapProxy, A as createHubCapForwardService, At as buildHeapSample, B as AGENT_CAP_FWD_ACTION, C as brokerTransportLink, Ct as runNpm, D as localProviderLink, Dt as HEAP_WATCH_INTERVAL_MS, E as ipcParentLink, Et as HEAP_RECLAIM_TRIGGER_MB, F as createUdsLogger, Ft as strandedMb, G as callWithServiceDiscovery, H as CapRouteResolver, I as createUdsLoggerWithControl, J as UdsLocalTransportServer, K as createLocalTransport, L as LocalChildClient, M as createUdsEventBridge, Mt as shouldReclaim, N as createUdsEventBus, Nt as startHeapWatch, O as HUB_CAP_FWD_ACTION, Ot as HEAP_WATCH_WARN_RATIO, P as udsChildLogToWorkerEntry, Pt as startRunnerHeapWatch, Q as encodeFrame, R as LocalChildRegistry, S as brokerCallForCap, St as resolveNpmInvocation, T as ipcChildLink, Tt as HEAP_RECLAIM_MIN_INTERVAL_MS, U as CapRouteError, V as AGENT_CAP_FWD_SERVICE, W as classifyCapRoute, X as localEndpointPath, Y as SocketChannel, Z as FrameDecoder, _ as createKernelHwAccel, _t as CapabilityHandle, a as getWorkerDeviceRegistry, b as __resetCapUsageRegistryForTests, bt as installManifestNativeDeps, c as setHubConnected, ct as NATIVE_PROVIDER_SERVICE_INFIX, d as getBrokerEventBus, dt as capBareAction, et as buildUdsNativeCapProxy, f as getMoleculerEventStats, ft as capServiceName, g as AddonDepsManager, gt as DeviceRegistry, h as subscribePassthrough, ht as serializeTypedArrays, i as createUdsAddonContext, it as mountNativeCapService, j as createParentUnownedCallHandler, jt as createV8Reclaimer, k as HUB_CAP_FWD_SERVICE, kt as RUNNER_HEAP_WATCH_INTERVAL_MS, l as EVENT_TOPIC_PREFIX, lt as capActionName, m as setNodeEventInterest, mt as deserializeTypedArrays, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as createAddonService, p as registerEventBusService, pt as parseCapAction, q as UdsLocalTransportClient, r as createAddonContext, s as getOrInitReadinessRegistryForClient, st as validateProviderRegistrations, t as installManifestPythonDeps, tt as createBrokerDeviceManagerApi, u as clusterEventTopic, ut as capActionSuffix, v as resolveHwAccel, vt as CapabilityUnavailableError, w as buildLinkChain, wt as createAddonDataPlaneFacility, x as getCapUsageRegistry, xt as resolveAddonClass, y as CapUsageRegistry, yt as copyBundledNativeModules, z as UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-5ei2xVUh.mjs";
27
+ import { $ as buildNativeCapProxy, A as createHubCapForwardService, At as buildHeapSample, B as AGENT_CAP_FWD_ACTION, C as brokerTransportLink, Ct as runNpm, D as localProviderLink, Dt as HEAP_WATCH_INTERVAL_MS, E as ipcParentLink, Et as HEAP_RECLAIM_TRIGGER_MB, F as createUdsLogger, Ft as strandedMb, G as callWithServiceDiscovery, H as CapRouteResolver, I as createUdsLoggerWithControl, J as UdsLocalTransportServer, K as createLocalTransport, L as LocalChildClient, M as createUdsEventBridge, Mt as shouldReclaim, N as createUdsEventBus, Nt as startHeapWatch, O as HUB_CAP_FWD_ACTION, Ot as HEAP_WATCH_WARN_RATIO, P as udsChildLogToWorkerEntry, Pt as startRunnerHeapWatch, Q as encodeFrame, R as LocalChildRegistry, S as brokerCallForCap, St as resolveNpmInvocation, T as ipcChildLink, Tt as HEAP_RECLAIM_MIN_INTERVAL_MS, U as CapRouteError, V as AGENT_CAP_FWD_SERVICE, W as classifyCapRoute, X as localEndpointPath, Y as SocketChannel, Z as FrameDecoder, _ as createKernelHwAccel, _t as CapabilityHandle, a as getWorkerDeviceRegistry, b as __resetCapUsageRegistryForTests, bt as installManifestNativeDeps, c as setHubConnected, ct as NATIVE_PROVIDER_SERVICE_INFIX, d as getBrokerEventBus, dt as capBareAction, et as buildUdsNativeCapProxy, f as getMoleculerEventStats, ft as capServiceName, g as AddonDepsManager, gt as DeviceRegistry, h as subscribePassthrough, ht as serializeTypedArrays, i as createUdsAddonContext, it as mountNativeCapService, j as createParentUnownedCallHandler, jt as createV8Reclaimer, k as HUB_CAP_FWD_SERVICE, kt as RUNNER_HEAP_WATCH_INTERVAL_MS, l as EVENT_TOPIC_PREFIX, lt as capActionName, m as setNodeEventInterest, mt as deserializeTypedArrays, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as createAddonService, p as registerEventBusService, pt as parseCapAction, q as UdsLocalTransportClient, r as createAddonContext, s as getOrInitReadinessRegistryForClient, st as validateProviderRegistrations, t as installManifestPythonDeps, tt as createBrokerDeviceManagerApi, u as clusterEventTopic, ut as capActionSuffix, v as resolveHwAccel, vt as CapabilityUnavailableError, w as buildLinkChain, wt as createAddonDataPlaneFacility, x as getCapUsageRegistry, xt as resolveAddonClass, y as CapUsageRegistry, yt as copyBundledNativeModules, z as UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-Cb1ANt3i.mjs";
28
28
  import { n as getSinglePidStats, t as getPidStats } from "./resource-monitor-BWmQ5i-o.mjs";
29
29
  import { t as CustomActionRegistry } from "./custom-action-registry-F__gp_VX.mjs";
30
30
  import { z } from "zod";
@@ -5360,6 +5360,7 @@ var CapabilityRegistry = class CapabilityRegistry {
5360
5360
  const state = {
5361
5361
  definition,
5362
5362
  providers: /* @__PURE__ */ new Map(),
5363
+ providerOwners: /* @__PURE__ */ new Map(),
5363
5364
  activeAddonId: null,
5364
5365
  router: null,
5365
5366
  disabledProviders: /* @__PURE__ */ new Set()
@@ -5417,8 +5418,15 @@ var CapabilityRegistry = class CapabilityRegistry {
5417
5418
  * Double-register of the same pair is always a programmer error —
5418
5419
  * two legitimately different addons implementing the same cap keep
5419
5420
  * using different `addonId`s and the user picks one via `configReader`.
5420
- */
5421
- registerProvider(capabilityName, addonId, provider) {
5421
+ *
5422
+ * `owner` (optional) stamps the registration with the incarnation that made
5423
+ * it — see {@link ProviderOwnerToken}. Once stamped, only an unregister
5424
+ * presenting the SAME token can delete it. Registration paths whose key is
5425
+ * reused across process generations (the UDS child manifest, the D3
5426
+ * `registerNode` handshake) MUST pass one; ownerless registrations keep the
5427
+ * historical unconditional behaviour.
5428
+ */
5429
+ registerProvider(capabilityName, addonId, provider, owner) {
5422
5430
  let state = this.capabilities.get(capabilityName);
5423
5431
  if (!state) {
5424
5432
  const colonIdx = capabilityName.indexOf(":");
@@ -5450,6 +5458,7 @@ var CapabilityRegistry = class CapabilityRegistry {
5450
5458
  if (missing.length > 0) throw new Error(`CapabilityRegistry: provider "${addonId}" for capability "${capabilityName}" declares exposesDeviceSettings: true but is missing DeviceSettingsContribution method(s): ${missing.join(", ")}. Implement them on the provider (or drop the flag from the cap def).`);
5451
5459
  }
5452
5460
  state.providers.set(addonId, provider);
5461
+ if (owner !== void 0) state.providerOwners.set(addonId, owner);
5453
5462
  if (state.definition.mode === "singleton") {
5454
5463
  const userChoice = this.configReader?.(capabilityName);
5455
5464
  const preferred = state.definition.preferredProvider;
@@ -5480,7 +5489,8 @@ var CapabilityRegistry = class CapabilityRegistry {
5480
5489
  tags: { addonId },
5481
5490
  meta: {
5482
5491
  capability: capabilityName,
5483
- mode: state.definition.mode
5492
+ mode: state.definition.mode,
5493
+ ...owner === void 0 ? {} : { owner }
5484
5494
  }
5485
5495
  });
5486
5496
  this.emitEvent("capability:provider-registered", {
@@ -5527,11 +5537,70 @@ var CapabilityRegistry = class CapabilityRegistry {
5527
5537
  const declarers = this.manifestDeclarers.get(capabilityName);
5528
5538
  return declarers ? [...declarers] : [];
5529
5539
  }
5530
- /** Unregister a provider. For singleton: clears active. For collection: removes from list. */
5531
- unregisterProvider(capabilityName, addonId) {
5540
+ /**
5541
+ * The owner token a registration declared, or `null` when the pair is not
5542
+ * registered or was registered without one.
5543
+ */
5544
+ getProviderOwner(capabilityName, addonId) {
5545
+ return this.capabilities.get(capabilityName)?.providerOwners.get(addonId) ?? null;
5546
+ }
5547
+ /**
5548
+ * Hand an EXISTING registration to a new owner, without touching the provider
5549
+ * instance, the active-singleton selection or any event.
5550
+ *
5551
+ * The takeover case: a replacement runner handshakes with an UNCHANGED cap
5552
+ * set, so the manifest diff correctly leaves the registration alone — but the
5553
+ * registration is still stamped with the dead generation's token, and nothing
5554
+ * the LIVE generation can present would ever match it. Re-stamping keeps the
5555
+ * capability cleanable when the successor eventually dies.
5556
+ *
5557
+ * Returns `false` when the pair is not registered (nothing to hand over) —
5558
+ * the caller decides whether that is a register-instead or a diagnosis.
5559
+ */
5560
+ reassignProviderOwner(capabilityName, addonId, owner) {
5561
+ const state = this.capabilities.get(capabilityName);
5562
+ if (!state || !state.providers.has(addonId)) return false;
5563
+ const previous = state.providerOwners.get(addonId);
5564
+ if (previous === owner) return true;
5565
+ state.providerOwners.set(addonId, owner);
5566
+ this.logger.info("Capability registration ownership reassigned", {
5567
+ tags: { addonId },
5568
+ meta: {
5569
+ capability: capabilityName,
5570
+ owner,
5571
+ previousOwner: previous ?? null
5572
+ }
5573
+ });
5574
+ return true;
5575
+ }
5576
+ /**
5577
+ * Unregister a provider. For singleton: clears active. For collection:
5578
+ * removes from list.
5579
+ *
5580
+ * `owner` is the caller's PROOF that it owns the registration it is deleting.
5581
+ * When the registration declared an owner (see {@link ProviderOwnerToken})
5582
+ * and the caller presents a different one — or none at all — this is a
5583
+ * logged no-op: the caller belongs to a generation that no longer holds the
5584
+ * key. This is what stops a dead runner's teardown from erasing its
5585
+ * successor's live registration ([D188](../../../../docs/decisions/adr-0188-an-unregister-carries-proof-of-ownership.md)).
5586
+ */
5587
+ unregisterProvider(capabilityName, addonId, owner) {
5532
5588
  const state = this.capabilities.get(capabilityName);
5533
5589
  if (!state) return;
5590
+ const registeredOwner = state.providerOwners.get(addonId);
5591
+ if (registeredOwner !== void 0 && registeredOwner !== owner) {
5592
+ this.logger.info("Unregister ignored — registration is owned by another incarnation", {
5593
+ tags: { addonId },
5594
+ meta: {
5595
+ capability: capabilityName,
5596
+ registeredOwner,
5597
+ presentedOwner: owner ?? null
5598
+ }
5599
+ });
5600
+ return;
5601
+ }
5534
5602
  state.providers.delete(addonId);
5603
+ state.providerOwners.delete(addonId);
5535
5604
  if (state.definition.mode === "singleton" && state.activeAddonId === addonId) {
5536
5605
  const userChoice = this.configReader?.(capabilityName);
5537
5606
  const preferred = state.definition.preferredProvider;
@@ -94548,6 +94617,11 @@ var JobJournal = class {
94548
94617
  function safeSegment(name) {
94549
94618
  return name.replace(/[^a-zA-Z0-9._-]/g, "_");
94550
94619
  }
94620
+ /** An exact semver — the only shape the staged package.json can be compared
94621
+ * against literally. Anything else ('latest', 'beta', a range) is a dist-tag
94622
+ * the FETCHER resolves; comparing the resolved version to the literal tag
94623
+ * failed every tag-based update (30/30 on 2026-08-20). */
94624
+ var EXACT_SEMVER = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
94551
94625
  var StagingArea = class {
94552
94626
  stagingDir;
94553
94627
  deps;
@@ -94565,8 +94639,12 @@ var StagingArea = class {
94565
94639
  if (input.signal.aborted) throw new Error("staging aborted after fetch");
94566
94640
  await this.deps.extract(tgz, destDir);
94567
94641
  const raw = JSON.parse(fs$17.readFileSync(path$39.join(destDir, "package.json"), "utf-8"));
94568
- if (raw.version !== input.version) throw new Error(`staging validation failed: ${input.name} expected version ${input.version}, got ${String(raw.version)}`);
94569
- return { stagedPath: destDir };
94642
+ if (typeof raw.version !== "string" || raw.version.length === 0) throw new Error(`staging validation failed: ${input.name} staged a package.json with no version`);
94643
+ if (EXACT_SEMVER.test(input.version) && raw.version !== input.version) throw new Error(`staging validation failed: ${input.name} expected version ${input.version}, got ${raw.version}`);
94644
+ return {
94645
+ stagedPath: destDir,
94646
+ resolvedVersion: raw.version
94647
+ };
94570
94648
  }
94571
94649
  cleanup(jobId) {
94572
94650
  fs$17.rmSync(path$39.join(this.stagingDir, jobId), {
@@ -19,6 +19,18 @@ export type ConfigReader = (capability: string) => string | undefined;
19
19
  * {@link ConfigReader} does for singletons.
20
20
  */
21
21
  export type CollectionConfigReader = (capability: string) => readonly string[] | undefined;
22
+ /**
23
+ * Proof that an unregister owns the registration it is about to delete.
24
+ *
25
+ * A `(capability, addonId)` key is NOT unique over time: a runner replaced by
26
+ * an update re-registers under the exact same key (`resolveRunnerId` is
27
+ * deterministic), so the dead generation's teardown and the live generation's
28
+ * registration are indistinguishable by key alone. The owner token names the
29
+ * INCARNATION that made a registration — e.g. `hub/recorder#7` — and
30
+ * {@link CapabilityRegistry.unregisterProvider} refuses any unregister that
31
+ * cannot present it. See docs/decisions/adr-0188-an-unregister-carries-proof-of-ownership.md.
32
+ */
33
+ export type ProviderOwnerToken = string;
22
34
  export declare class CapabilityRegistry {
23
35
  private readonly capabilities;
24
36
  private readonly logger;
@@ -148,8 +160,15 @@ export declare class CapabilityRegistry {
148
160
  * Double-register of the same pair is always a programmer error —
149
161
  * two legitimately different addons implementing the same cap keep
150
162
  * using different `addonId`s and the user picks one via `configReader`.
163
+ *
164
+ * `owner` (optional) stamps the registration with the incarnation that made
165
+ * it — see {@link ProviderOwnerToken}. Once stamped, only an unregister
166
+ * presenting the SAME token can delete it. Registration paths whose key is
167
+ * reused across process generations (the UDS child manifest, the D3
168
+ * `registerNode` handshake) MUST pass one; ownerless registrations keep the
169
+ * historical unconditional behaviour.
151
170
  */
152
- registerProvider(capabilityName: string, addonId: string, provider: unknown): void;
171
+ registerProvider(capabilityName: string, addonId: string, provider: unknown, owner?: ProviderOwnerToken): void;
153
172
  /**
154
173
  * Whether a provider for the exact `(capabilityName, addonId)` pair is
155
174
  * currently registered. Lets a re-registration path (e.g. the agent's
@@ -173,8 +192,37 @@ export declare class CapabilityRegistry {
173
192
  * it is the diagnosis).
174
193
  */
175
194
  getManifestDeclarers(capabilityName: string): readonly string[];
176
- /** Unregister a provider. For singleton: clears active. For collection: removes from list. */
177
- unregisterProvider(capabilityName: string, addonId: string): void;
195
+ /**
196
+ * The owner token a registration declared, or `null` when the pair is not
197
+ * registered or was registered without one.
198
+ */
199
+ getProviderOwner(capabilityName: string, addonId: string): ProviderOwnerToken | null;
200
+ /**
201
+ * Hand an EXISTING registration to a new owner, without touching the provider
202
+ * instance, the active-singleton selection or any event.
203
+ *
204
+ * The takeover case: a replacement runner handshakes with an UNCHANGED cap
205
+ * set, so the manifest diff correctly leaves the registration alone — but the
206
+ * registration is still stamped with the dead generation's token, and nothing
207
+ * the LIVE generation can present would ever match it. Re-stamping keeps the
208
+ * capability cleanable when the successor eventually dies.
209
+ *
210
+ * Returns `false` when the pair is not registered (nothing to hand over) —
211
+ * the caller decides whether that is a register-instead or a diagnosis.
212
+ */
213
+ reassignProviderOwner(capabilityName: string, addonId: string, owner: ProviderOwnerToken): boolean;
214
+ /**
215
+ * Unregister a provider. For singleton: clears active. For collection:
216
+ * removes from list.
217
+ *
218
+ * `owner` is the caller's PROOF that it owns the registration it is deleting.
219
+ * When the registration declared an owner (see {@link ProviderOwnerToken})
220
+ * and the caller presents a different one — or none at all — this is a
221
+ * logged no-op: the caller belongs to a generation that no longer holds the
222
+ * key. This is what stops a dead runner's teardown from erasing its
223
+ * successor's live registration ([D188](../../../../docs/decisions/adr-0188-an-unregister-carries-proof-of-ownership.md)).
224
+ */
225
+ unregisterProvider(capabilityName: string, addonId: string, owner?: ProviderOwnerToken): void;
178
226
  /** Enable a previously disabled collection provider. */
179
227
  enableCollectionProvider(capability: string, addonId: string): void;
180
228
  /** Disable a collection provider (keeps it registered but excluded from active list). */
@@ -2,7 +2,7 @@
2
2
  * The bare specifiers an ESM addon may leave unbundled and expect the HOST
3
3
  * framework to provide. Self-contained addons (the standard) inline
4
4
  * @camstack/types, @camstack/sdk, zod, @trpc/* — so for them only
5
- * @camstack/system + @camstack/shm-ring survive. Framework-mode (legacy)
5
+ * @camstack/system survives as genuinely host-provided. Framework-mode (legacy)
6
6
  * addons additionally leave the types/sdk/zod/trpc set external. Listing the
7
7
  * full union here lets one resolver hook serve both modes: for a self-contained
8
8
  * addon the extra entries simply never appear as live imports.
@@ -17,7 +17,7 @@ export { buildCapRouters, builderMountedCapNames } from './cap-router-builder.js
17
17
  export type { CapRouterPrimitives, CapRouterServices, CapProviderGetter, RemoteProxyFactory, ProcedureBuilder, ProcedureResolverArgs, } from './cap-router-builder.js';
18
18
  export { describeProviderKindDrift } from './provider-kind-drift.js';
19
19
  export type { ProviderKindHint } from './provider-kind-drift.js';
20
- export type { CapabilityRouter, CapabilityRouterFactory, ConfigReader, } from './capability-registry.js';
20
+ export type { CapabilityRouter, CapabilityRouterFactory, ConfigReader, ProviderOwnerToken, } from './capability-registry.js';
21
21
  export { CustomActionRegistry, type CustomActionEntry } from './custom-action-registry.js';
22
22
  export { INFRA_CAPABILITIES, isInfraCapability } from './infra-capabilities.js';
23
23
  export type { InfraCapability } from './infra-capabilities.js';
@@ -16,6 +16,7 @@ export declare class StagingArea {
16
16
  constructor(stagingDir: string, deps: StagingAreaDeps);
17
17
  fetchAndStage(input: FetchAndStageInput): Promise<{
18
18
  stagedPath: string;
19
+ resolvedVersion: string;
19
20
  }>;
20
21
  cleanup(jobId: string): void;
21
22
  /**
@@ -222,6 +222,18 @@ export type ParentToChildRequest = CapCallMessage | ParentEventMessage | Readine
222
222
  export interface RegisteredChild {
223
223
  readonly childId: string;
224
224
  readonly caps: readonly ChildCapDescriptor[];
225
+ /**
226
+ * Monotonic id of the CONNECTION this registration arrived on, unique within
227
+ * the parent process.
228
+ *
229
+ * `childId` is deterministic (`resolveRunnerId`), so a runner replaced by an
230
+ * update reconnects under the exact same id — the two generations are
231
+ * indistinguishable without this. Consumers stamp it onto whatever they
232
+ * derive from the child (capability registrations, subtree manifests) so a
233
+ * teardown arriving from the DEAD generation can be told apart from the live
234
+ * one. See docs/decisions/adr-0188-an-unregister-carries-proof-of-ownership.md.
235
+ */
236
+ readonly incarnation: number;
225
237
  }
226
238
  /** Arguments to route a cap method call (the `cap-call` message minus its discriminant). */
227
239
  export type CapCallInput = Omit<CapCallMessage, 'kind'>;
@@ -139,6 +139,8 @@ export declare class LocalChildRegistry {
139
139
  private readonly children;
140
140
  private registeredHandler;
141
141
  private goneHandler;
142
+ /** Source of {@link RegisteredChild.incarnation}; one bump per accepted connection. */
143
+ private nextIncarnation;
142
144
  private eventHandler;
143
145
  private logHandler;
144
146
  private readinessHandler;
@@ -291,8 +293,13 @@ export declare class LocalChildRegistry {
291
293
  listChildren(): readonly RegisteredChild[];
292
294
  /** Register the (single) child-registered handler. Only one handler is active at a time. */
293
295
  onChildRegistered(handler: (child: RegisteredChild) => void): void;
294
- /** Register the (single) child-gone handler. Only one handler is active at a time. */
295
- onChildGone(handler: (childId: string) => void): void;
296
+ /**
297
+ * Register the (single) child-gone handler. Only one handler is active at a
298
+ * time. The handler receives the {@link RegisteredChild.incarnation} of the
299
+ * connection that closed — carry it into whatever teardown it triggers, so a
300
+ * dead generation cannot tear down its successor's work.
301
+ */
302
+ onChildGone(handler: (childId: string, incarnation: number) => void): void;
296
303
  /**
297
304
  * Register the (single) child-event handler. Invoked when a child sends an
298
305
  * event via `LocalChildClient.emitEvent`. Only one handler is active at a