Local lifecycle cookbook
A runnable walkthrough of the governed migration lifecycle on the SDK's in-memory runtime — configured authorization, platform bootstrap, planning, conversion, residue, evidence, certification, and offline verification.
This walkthrough is a single runnable file shipped with the SDK — see the
full listing.
It drives the whole governed lifecycle through createAirliftRuntime() composed with
createAirliftConfiguredAuthorization() — the composition intended for unit tests,
examples, and local demos — and it is executable documentation: the repository's test
suite runs it on every change, so every snippet below reflects contracts that
actually execute.
The cookbook is versioned against action manifest generation 11 and asserts the pin at startup:
import { AIRLIFT_ACTION_MANIFEST_GENERATION } from '@fabricorg/airlift';
if (AIRLIFT_ACTION_MANIFEST_GENERATION !== 11) {
throw new Error('This walkthrough is versioned against action manifest generation 11.');
}To run it, install @fabricorg/airlift, save the
full listing as
local-lifecycle.mjs, and run it with Node.js 22+:
npm install @fabricorg/airlift
node local-lifecycle.mjs ./cookbook-outIt also ships inside the npm package itself, at
node_modules/@fabricorg/airlift/examples/local-lifecycle.mjs, in releases
published in 0.16.3 and later.
Contributors working from a repository checkout can run it directly against the built package surface instead:
pnpm install
pnpm --filter @fabricorg/airlift build
node packages/airlift/examples/local-lifecycle.mjs ./cookbook-outConfigured authorization
The authorization directory is install-time configuration, not request input. Memberships bind natural persons to tenant roles; system and agent principals are admitted only through narrow trusted-id lanes.
const authorizationConfig = {
memberships: [
{ organizationId: ORG, principal: 'operator@acme.example', principalType: 'natural_person', role: 'admin' },
{ organizationId: ORG, principal: 'approver@acme.example', principalType: 'natural_person', role: 'approver' },
],
// System principals admitted to the narrow worker transition set.
trustedWorkerIds: ['svc-airlift-worker'],
// Agent principals admitted only to a bounded action set: assessment,
// dependency mapping, plan generation, inventory, conversion, artifacts,
// residue, discrepancy triage, and modernization recommendations. Agents can
// NEVER approve, certify, mint certificates, or execute cutover.
trustedAgentIds: ['agent-convert-01'],
// System principals admitted to the platform registry bootstrap boundary.
platformInstallerIds: ['svc-airlift-installer'],
// Tenant-bound principals permitted to submit independently produced evidence.
admittedValidationPrincipals: [
{ organizationId: ORG, principal: 'svc-airlift-worker', provider: 'experiments' },
],
};The runtime composes that directory with evidence services and an Ed25519 signer:
const runtime = createAirliftRuntime({
authorization: createAirliftConfiguredAuthorization(authorizationConfig),
services: {
// Admits a validation run only when producer, provider, run ref, evidence
// ref, and digest all match an immutable registry entry.
...configuredAirliftEvidenceServices(authorizationConfig, [evidenceRegistryEntry]),
airliftEvidenceSigner: createEd25519EvidenceSigner({ keyId, privateKeyPem }),
},
});Platform-tenant bootstrap
Organizations are minted from the platform registry tenant, never from an org
tenant. airlift.org_provision requires all three of:
tenantId = AIRLIFT_PLATFORM_TENANT('airlift-platform'),- a
systemactor listed inplatformInstallerIds, { orgId, name }params.
await runtime.invokeAction(AIRLIFT_ACTION_IDS.orgProvision, {
tenantId: AIRLIFT_PLATFORM_TENANT,
actorId: 'svc-airlift-installer',
actorType: 'system',
params: { orgId: 'acme-analytics', name: 'Acme Analytics' },
idempotencyKey: 'cookbook:org',
});Invoking it with the org's own tenant id fails with
Module "airlift" is not enabled for tenant …. That message reads like a defect but
is the installer boundary working as designed: the installer grant is
tenant-bound, so entitlement fails closed outside the platform tenant.
Happy-path ordering
Each stage lists its acting principal; prerequisites are explicit. The runnable script contains the full parameter shapes for every call.
- Estate —
estate_register(operator).environment: 'prod'engages the full gate policies. - Engagement —
engagement_createwithservices: ['discovery', 'migration_factory'], thenengagement_activate(operator). Discovery and factory actions require an active engagement. - Assessment —
assessment_record(operator) with tool version, report refs, digests, and object counts. - Inventory + dependency graph accept —
object_registerper object (operator);dependency_graph_startanddependency_batch_record(trusted agent);dependency_graph_acceptwith an explicitexpectedEdgeCount, thenassessment_acceptwithexpectedObjectCount(operator). Acceptance freezes scope digests. - Plan generate → select → freeze —
plan_generate(dependency-aware wave candidates plus economics),plan_select, thenengagement_freezebeforeplan_freeze(operator) — freezing a plan requires the engagement scope frozen first. - Wave plan/assign —
wave_plan,wave_assignper object (operator); objects reachplanned. - Conversion batch —
conversion_batch_create(operator),conversion_batch_start(worker), then per objectconversion_start/conversion_record(worker). Every converted object must carry an immutabletarget_codeartifact (artifact_register) whose digest matches the recordedoutputDigest. - Residue lane — for each failed conversion:
residue_create → residue_estimate(agent)→ residue_assign(operator). The batch then completes withcompleted_with_residue— completion requires every failed object to hold an active residue case. Remediation continues after completion:artifact_register+residue_resolve(operator),residue_reviewby a distinct natural person (approver), then a fresh governedconversion_start/conversion_recordbrings the repaired object fromreworkback toconverted. - Validation evidence admission —
validation_profile_assign(operator), thenvalidation_run_record(admitted principal). The run is admitted only because producer, provider, run ref, evidence ref, and digest all match the immutable registry entry — a caller's digest is never trusted merely because it is well formed. - Readiness tracks —
readiness_recordper required track of the assigned profile, admitting the validation run as evidence. - Parity certify —
parity_certifywith honest depth fields (see below). - Business acceptance —
business_acceptis a human decision by the approver. - Migration certificate mint —
migration_certificate_mintbinds the certificate to the exactexpectedProfileDigestandexpectedReadinessDigestthe caller observed; the Ed25519 signer signs the envelope digest. - Wave approval —
wave_approveby the approver (a natural person distinct from the operator). - Offline verification — the script verifies the envelope in-process, exports the envelope and public key, and prints the CLI command for you to run, fully offline:
fa certificate verify ./cookbook-out/migration-certificate.json --keys ./cookbook-out/verify-keys.jsonEvidence export
airlift.evidence_export assembles the replayable evidence pack for one wave
(or one migration object) from the org's event log — assessment, conversions
with tool versions, parity certificates with declared evidence depth,
approvals, and the governance revision in force at export time. It returns the
pack as reportJson alongside a sha256 reportDigest over that exact string,
and records the export itself as an AirliftEvidenceExported event keyed by
that digest — the audit stream proves what was attested without persisting the
pack contents.
The params contract requires a waveId or objectId plus a reason
(1–500 characters):
const result = await runtime.invokeAction(AIRLIFT_ACTION_IDS.evidenceExport, {
tenantId: ORG,
actorId: 'operator@acme.example',
actorType: 'natural_person',
params: { waveId, reason: 'Assemble the replayable evidence pack for the approved wave.' },
idempotencyKey: 'cookbook:evidence-export',
});
// result.data: { waveId, exportKey, reportDigest, reason, reportJson }Authorization requires the airlift:evidence:export permission, held by the
admin, operator, approver, and validator roles for natural-person principals
(the cookbook's operator is configured as admin).
Evidence export is deliberately not a trusted-worker action — unlike the
conversion and certification steps above, a system principal is denied even
though it drove most of the lifecycle. The cookbook demonstrates this: the
conversion worker's attempt fails closed with the same generic entitlement
error as the other module-boundary denials, before the operator invokes the
action successfully.
Expected denials are first-class outcomes
A suite of passing checks behaves identically with the boundary disabled, so the cookbook demonstrates each denial and fails if any of them unexpectedly succeeds:
| Attempt | Outcome |
|---|---|
org_provision from the org's own tenant | Entitlement fails closed: Module "airlift" is not enabled for tenant … |
Agent actor invoking wave_approve | Entitlement fails closed with the same generic error — approvals are outside the bounded agent action set, so the agent is not entitled to the module for this action at all |
Parity certificate claiming sampled_rows depth without aggregateChecksums | Policy blocks the overclaim: a certificate must carry the evidence for every depth it claims |
| Resolver reviewing their own residue | Policy blocks it: The resolver cannot independently review the same residue. |
System worker invoking evidence_export | Entitlement fails closed with the same generic error — evidence export is natural-person-only and deliberately not a trusted-worker action |
Idempotency keys
Every mutation passes an idempotencyKey following a stable
<demo>:<step>[:<subject>] convention (for example cookbook:cnv-record:<objectId>).
Within the same runtime, re-invoking with the same key collapses onto the original
invocation instead of duplicating the logical command — the cookbook demonstrates
this by registering the estate twice and asserting both calls return the same
estate id. The in-memory store lives only as long as the process, so a rerun of
the script starts from scratch; against the durable Postgres store the same keys
give cross-process replay safety.
The pre-cutover boundary
The local walkthrough stops at certified objects and an approved wave. It deliberately does not claim cutover execution or rehearsal, data transfer, workspace job runs, deployment evidence, or production operational evidence. Those stages require the durable runtime, real effectors, and independently produced evidence — a local in-memory demo proving them would be an overclaim, which is exactly what the governance model exists to prevent.