hivecast-sdk/packages/browser-host/src/boundary-identifiers.md
2026-06-07 23:11:26 +00:00

5.7 KiB

Boundary Identifiers — In-Package Reference

If you're editing files in this package, you must know which identifier you're touching. This file is the in-package boundary contract for the SDK browser host. Provider overlays may maintain their own deployment-specific mapping docs, but boundary names exported by this package stay provider-neutral.

The Four Boundary Identifiers

Boundary name What it identifies Example Where it crosses boundaries
subjectScope NATS subject prefix for this Host's actors SPACE-HIVECAST data-subject-scope attr, bootstrap.subjectScope JSON field, IBrowserBootstrapResponse.subjectScope
authority Who owns this surface (user/principal) test-user-1 data-authority attr, bootstrap.authority JSON field, IBrowserBootstrapResponse.authority
appName Which webapp this is director data-app-name attr, bootstrap.appName JSON field, matrix.json:appName
tabRuntimeId This browser tab's runtime identity test-user-1.DIRECTOR.SESSION-A1B2C3D4 data-tab-runtime-id attr, computed client-side from authority + appName + session id

The discipline: clear at the boundary, flexible inside

The names above are required at interface boundaries — anywhere code in this package talks to code outside it. That means:

  • HTTP bootstrap response fields
  • HTML attributes on <matrix-dsl-host>
  • Exported TypeScript interfaces, types, and function signatures
  • matrix.json fields

Inside private functions and private fields of this package, local variable names can use whatever feels natural for the surrounding code. The discipline is at the seams.

Rules

  1. Boundary-crossing values use the canonical boundary name. When you read a value from a config file, HTML attribute, JSON field, or exported interface, you read the canonical name (subjectScope, authority, appName, tabRuntimeId). You can rename it as a local variable inside your function if you want; just make sure the boundary read happened under the right name.

  2. One concept per boundary name. Never write setAttribute('root', ...) in new code. The root attribute is ambiguous — it has meant all four identifiers at different times in git history. Pick the specific one.

  3. No implicit substitution. If appName is missing from a bootstrap that needs it, throw. Do not silently degrade to using subjectScope in the appName slot; that creates wire-crossing bugs.

  4. Forbidden boundary names (do not introduce in new code at boundaries):

    • root — meant all four at different times
    • realm — alias of root, same problem
    • baseRoot — got passed in as appName but is named after subjectScope
    • rootAttr — generic, ambiguous
    • daemonRoot / daemonRealm — pre-Host-Service legacy
    • busRoot / authorityRoot / runtimeRoot — these were in-flight rename drafts; superseded by subjectScope / authority / tabRuntimeId
    • transportRoot — classify each site; if the value has .SESSION-XXXX, the boundary name is tabRuntimeId; otherwise it's subjectScope
    • spaceRoot — use authority
    • appRoot — use appName or tabRuntimeId depending on which
  5. Allowed legacy aliases (compat only at boundaries, one release):

    • bootstrap.root — still emitted for one release; readers must migrate
    • bootstrap.platformRoot — synonym of subjectScope during transition
    • bootstrap.addressRoot — synonym of authority during transition
    • bootstrap.busRoot / bootstrap.authorityRoot / bootstrap.runtimeRoot — in-flight rename names from earlier drafts; emit both old and new for one release so any consumer already coded against them keeps working

HTML attribute writes are intent-explicit

When you write attributes during browser bootstrap, write the specific canonical attribute name:

this.setAttribute('data-subject-scope', subjectScope);  // not 'root', not 'realm'
this.setAttribute('data-authority', authority);
this.setAttribute('data-app-name', appName);
this.setAttribute('data-tab-runtime-id', tabRuntimeId);

The legacy root attribute is still written during the transition release (transport readers in older code path still consume it as subjectScope in hosted mode). New code reads data-subject-scope instead.

Why this exists

Through git history, "root" has meant all four of these identifiers at different times. commit d73fc73ee (May 14, 2026) introduced busRoot as a separate bootstrap field when Space-claimed users couldn't load the platform shell. That fix worked for transport, but the HTML attribute root is read by THREE consumers expecting THREE different meanings, so writing busRoot into it broke tab identity downstream (tab_realm.ts:generateIdentityTabRoot, whose appName parameter was being fed busRoot until this workstream landed).

The lesson: at boundaries, names that say what they mean prevent the wire-crossing class of bug. Internal variable names can stay flexible; the boundary names cannot.

Where each identifier comes from

  • subjectScope — server-side, derived from status.transport.root of whichever Host's gateway answered the HTTP request
  • authority — server-side, the signed-in principal's primary_authority_root (or, on Device hosts, the Device's linked authority from host-auth.json)
  • appName — server-side, derived from the route's app context (/apps/director/director); declared in the package's matrix.json
  • tabRuntimeId — client-side, generated as ${authority}.${appName.toUpperCase()}.SESSION-${sessionId}, persisted in sessionStorage