TechFabricTechFabricPlatform
Platform referenceReference

Platform Host 6.x

Install and operate the durable governed-action host for applications called by Hermes, Harness, MCP, and other agent runtimes.

@fabricorg/platform-host is the application-side runtime for governed mutations. It creates the durable invocation before dispatch, validates the registered action schema, enforces authorization and policy, coordinates adapters, appends canonical events, and supports approval and interruption recovery.

An external agent such as Hermes does not install this package merely to call a Fabric application. The application installs Platform Host and exposes a narrow authenticated REST or MCP gateway. The agent receives only that gateway, a registration-bound credential, and the actions allowed by its current grant.

IntegratorInstall @fabricorg/platform-host?Correct boundary
Application or vertical ownerYesRegister actions and run the governed Host
Hermes, TechFabric Harness, or another external agentNoDiscover and invoke the application's REST/MCP gateway
Durable worker owned by the applicationYesCall executeInvocation() for a persisted invocation

Install

Platform Host 6.x requires Node.js 20 or newer, Platform 1.1 or newer, and the Assembly peer:

pnpm add @fabricorg/platform@^1.2.0 @fabricorg/platform-host@^6.0.0 @fabricorg/assembly@^0.3.0
npm install @fabricorg/platform@^1.2.0 @fabricorg/platform-host@^6.0.0 @fabricorg/assembly@^0.3.0

The package supports ESM and CommonJS. Version 6.0.0 publishes Host contract generation 2. Applications should record both values in runtime evidence because the npm version and durable contract generation evolve independently.

Wire the host

The ingress layer authenticates the caller and creates any immutable admission record. It then submits a trusted command to the Host. Do not pass credentials, bearer tokens, signatures, nonces, or private provider configuration as action parameters.

import {
  createGovernedActionHost,
  PostgresPlatformHostStore,
} from "@fabricorg/platform-host";

const store = new PostgresPlatformHostStore(
  domainStore,
  pool,
  {
    run: async (run) => {
      const client = await pool.connect();
      try {
        await client.query("BEGIN");
        const result = await run({
          db: domainStore.withClient(client),
          sql: client,
        });
        await client.query("COMMIT");
        return result;
      } catch (error) {
        await client.query("ROLLBACK");
        throw error;
      } finally {
        client.release();
      }
    },
  },
);

await store.ensureSchema();

const host = createGovernedActionHost({
  store,
  resolveAction: (actionId) => actionCatalog.get(actionId),
  authorization: {
    checkEntitlement,
    authorize: authorizeSubmission,
    authorizeExecution: async ({
      invocation,
      parameters,
      executionReason,
    }) =>
      admissionStore.revalidate({
        admissionId: invocation.authorizationBindingId,
        tenantId: invocation.tenantId,
        actorId: invocation.actorId,
        actionId: invocation.actionId,
        parameters,
        executionReason,
      }),
  },
  runtimeEvidence: {
    hostPackageVersion: deployedHostPackageVersion,
    policyRulesetVersion: "application-policy.v1",
  },
});

domainStore.withClient(client) is application-owned: it must return the same domain-store interface bound to the supplied PostgreSQL/Lakebase transaction. The transaction provider is what allows domain writes, event sequence allocation, canonical event append, and invocation finalization to commit or roll back as one unit.

Production applications use PostgresPlatformHostStore with Lakebase or PostgreSQL and run ensureSchema() during controlled startup or migration. MemoryPlatformHostStore is for tests and explicit local development.

Bind execution to the approved composition

For an assembled application, pass the exact lockfile and the full explicit module registry:

const host = createGovernedActionHost({
  store,
  registry,
  authorization,
  composition: {
    assembly: approvedAssembly,
    resolveInitiatingReleaseDigest: (submission) =>
      releaseRegistry.resolveTrustedDigest(submission.provenance),
  },
});

Every registered FabricModule must carry its published package version and compiler-derived manifestDigest. Host derives loaded capability identities from registry.orderedModules; it does not accept a parallel caller-supplied artifact list. Startup fails on a missing, extra, duplicate, version-mismatched, or digest-mismatched capability.

For each new invocation, Host stores the trusted assembly digest and, when resolved, the initiating promoted release digest in runtimeEvidence. Release lookup failure or a malformed digest fails before invocation creation. PostgreSQL stores these fields in the existing JSONB runtime-evidence document, so no schema column is added.

See Production adoption for the lockfile and deployment-check sequence.

Submit an admitted agent command

Create the admission before starting background orchestration. Persist only an opaque, non-secret binding on the invocation:

const admission = await admissionStore.admit({
  tenantId,
  registrationId,
  actorId,
  actionId,
  parameters,
  idempotencyKey,
});

const submitted = await host.submitAction({
  actionId,
  parameters,
  tenantId,
  spaceId,
  actorId,
  actorType: "agent",
  idempotencyKey,
  authorizationBindingId: admission.id,
  correlationId,
});

The same logical request must reuse the same idempotency key and canonical parameters. Platform Host binds new idempotent invocation rows to the action version, actor/authority binding, and a fabric-canonical-json-sha256-v1 parameter digest. In audit-only mode a changed command recovers the old invocation and calls onConflict; in enforce mode it throws IdempotencyConflictError with code IDEMPOTENCY_CONFLICT. Legacy rows without a digest skip only the unavailable parameter comparison and call onLegacyRecord; actor, authority-binding, and action-version mismatches still reject in enforce mode. Host defaults to enforcement; set conflictMode: "audit-only" for the documented preflight soak and rollback window.

Submission authorization answers whether the caller may create the durable invocation. authorizeExecution revalidates the original actor, schema-parsed durable parameters, current registration and resource scope immediately before policies and mutation code. It runs with an executionReason of:

  • initial for first execution;
  • approval_resume after an approval decision;
  • recovery for interrupted work.
  • offline_replay for a command captured while disconnected and submitted for reconciliation.

Actions may declare execution.authorityMoment as capture, execution, or both. An offline replay whose execution-time authority is denied returns reconciliation_required with a structured outcome; it is neither silently executed nor discarded. AuthorizationBinding contains audit-safe evidence and an opaque binding ID, never credentials or signed principal proofs. For execution and both, the Host detects an expired binding before mutation. resourceScope is an opaque identifier interpreted by the application-owned authorizeExecution policy, not by core.

Invocation provenance

SubmitActionInput.provenance accepts an InvocationProvenance envelope that separates bounded trace attributes from allowlisted durable audit attributes. Attribute names are namespaced (domain.key), values and counts are limited, and audit attributes fail closed without an allowlist and pass through the configured redactor. Restricted outbox payloads omit provenance unless a classification-aware host policy opts in. SDUI integrations use experience.documentId, experience.compositionId, and experience.releaseId as audit attributes; channel and interaction detail remain trace-only. Core does not interpret experience vocabulary.

provenance: {
  source: "sdui",
  correlationId,
  auditAttributes: {
    "experience.documentId": documentId,
    "experience.compositionId": compositionId,
    "experience.releaseId": releaseId,
  },
  traceAttributes: { "experience.channel": "pos" },
}

For execution and both, revocation can therefore stop work that was admitted earlier but has not executed. capture requires durable capture evidence and intentionally governs by that recorded moment. An already completed idempotent replay remains observable and does not execute again.

Events, adapters, and recovery

Use eventPhase: "before_adapters" for durable intent/outbox events. With the PostgreSQL transaction provider, the handler's domain writes and those declared events commit atomically.

Use eventPhase: "after_adapters" only for completion or attestation events whose truth requires every configured adapter to have succeeded. In Host 6.x, the completion event and invocation completion commit together. If finalization fails after an adapter succeeds, recovery reuses the succeeded adapter checkpoint and retries finalization without repeating the external effect.

Adapters still need a stable provider idempotency identity and an explicit reconciliation path for ambiguous remote outcomes. A non-idempotent action with unknown side effects must fail closed for operator reconciliation; it must not blindly rerun after a stale lease.

State-machine guards run with trusted entity state, actor, schema-parsed parameters, invocation ID, and transaction-bound database context. A guard that returns false or throws fails closed before the handler runs. Transaction providers should expose transaction-scoped getEntityState() so the read, domain write, event append, and invocation finalization use the same database snapshot.

Dispatcher submission failures leave the invocation pending. Re-submit the same idempotency key to retry the stable workflow ID. A kind: "saga" action must be mapped to a durable worker-side SagaImplementation; direct execution through the atomic Host path is refused.

Runtime observability and health

Pass a vendor-neutral telemetry callback or { record } sink to the Host, action worker cycle, or outbox relay. Lifecycle records use the stable names exported by PLATFORM_HOST_METRIC_NAMES. Telemetry is best effort, so a metrics backend outage cannot alter governed execution.

import { getPlatformHostHealthSnapshot } from "@fabricorg/platform-host";

const health = await getPlatformHostHealthSnapshot({
  store,
  tenantId,
  spaceId,
  workers: [{ lastHeartbeatAt, staleAfterMs: 30_000 }],
});

Health responses contain only tenant/space-scoped aggregate counts: invocation backlog, running and expired leases, approval waits, reconciliation-required work, outbox backlog, expired leases, and dead letters. Parameters, results, actors, event payloads, and errors are never returned. An unavailable source is unhealthy and not ready; stale workers and operational lease, dead-letter, or reconciliation findings are reported as readiness reason codes. Approval waits remain visible without making a healthy worker pool unready.

Agent-facing gateway contract

The application gateway—not the model—must:

  1. authenticate a tenant- and registration-bound credential;
  2. derive actorId and actorType server-side;
  3. filter discovery to explicitly reviewed actions in the current grant;
  4. require a stable idempotency key for every mutation;
  5. stage private or large content and submit only an opaque reference where necessary;
  6. pass the opaque admission ID as authorizationBindingId;
  7. surface actionInvocationId, status, approval state, and privacy-safe errors;
  8. provide status and reconciliation operations scoped to the same registration.

The agent must never call action handlers, adapters, Temporal workflows, or the database directly. Temporal may dispatch and recover a Host invocation, but it is not a second mutation authority.

Production checklist

  • Register each mutation as a FabricModule action with a stable schema and declared events.
  • Configure both submission and execution-time authorization for delegated agents.
  • Use a transaction provider for atomic domain writes and canonical events.
  • Configure a durable dispatcher/worker; inline execution is only for tests and local development.
  • Keep secrets out of parameters and use redactActionParameters as defense in depth.
  • Use stable command and provider idempotency identities.
  • Revalidate resource scope before prospect-visible or otherwise external adapter effects.
  • Resume approvals through resumeApprovedInvocation() instead of calling mutation code directly.
  • Rebuild projections from listEvents() with sequence-monotonic checkpoints.
  • Test policy denial, tenant isolation, revocation, adapter failure, crash recovery, and idempotent replay.
  • Certify PostgreSQL or Lakebase migrations, concurrency, failover, backup, and restore in the target environment; this repository's SQL adapter suite is deterministic rather than a live database test.

See Agent HITL for approval routing and Mutation pipeline for stage ordering.

On this page