npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@clossys/builder

v0.11.0

Published

Declared reality made actual: an idempotent manifest engine and machine verification, a deployment surface contract with read-only platform adapters, a toolchain pin (runtime, package manager, build order), the liveStateSurface contract and shared CI gate

Readme

@clossys/builder

Declared reality made actual. A runtime pin, a machine manifest, and a deployment target are the same statement at three altitudes — this is what should exist; go and see whether it does. This package holds the manifest engine, the deployment surface contract, the toolchain pin, the shared liveStateSurface reconciliation contract all three of those sit on, the CI gate mechanics that reconcile any of them for real, and an observation-bundle transport for the fleet's own inverted evaluation model — each repository observes its own compliance in its own CI, and a plane aggregates what got published.

npm install @clossys/builder

This package is published to the public npm registry, https://registry.npmjs.org. Installing it needs no authentication: no npm token, no .npmrc registry override, and no GitHub credential of any kind.

Desired-state realization rate

Independent consumer evidence shows the position's owned metric meets its setpoint over the declared review cadence. The owned metric is desired-state realization rate, computed by assessDesiredStateRealizationRate(). An empty evaluated set is indeterminate, never a perfect rate of 1. This package does not measure consumer evidence and does not close the loop; a consumer binds the condition against their own declared live-state subjects and independent observations. A green run of this package's tests is not a close. An offline declaration check is not live proof.

import { assessDesiredStateRealizationRate } from "@clossys/builder";

const report = assessDesiredStateRealizationRate(input);
builder-check assessment.json

The command prints JSON and exits 0 for satisfied, 1 for violated, and 2 for indeterminate, unreadable, or invalid input.

Hub inventory scoping (#997)

The assessment input may optionally carry a hubInventory document — { "schemaVersion": 1, "repositories": [{ "id": "..." }] } — naming the repositories the account hub inventories. It is pure caller input: this module never reads a hub marker, an inventory file, or gh, and an absent hubInventory changes nothing.

When a well-formed inventory is supplied, reconciliation is scoped to it:

  • every declared subject must name the repository it belongs to (repositoryId); one without it is a subject-repository-id-required finding;
  • a subject whose repositoryId the inventory does not list is flagged unlisted and excluded from the rate — it is the hub's reconciliation work to list a repository, not this metric's job to certify one it was never told about; and
  • observations tied to unlisted subjects are never counted as evidence.

A malformed inventory never scopes anything — every declared subject stays in the denominator, and the hub-inventory-shape finding is the caller's signal that the run was not scoped.

This package declares that command as its first-day assessment surface in its own manifest:

"foundry": { "assessment": { "bin": "builder-check", "invocation": "single-json-input" } }

Onboarding discovers that declaration from the installed manifest and never infers a surface. builder-verify-toolchain and builder-verify-machine stay flag-shaped tooling CLIs; they are not the assessment surface. Builder is not a required first-day role; Advisor remains the only required first-day assessment.

The job

Builder owns the gap between what a plane declared and what the machine, the platform, or the pipeline actually is — whether the subject is a toolchain, a manifest, or a deployment target. It never guesses and it never lets a green offline check stand in for a live answer it never actually checked.

liveStateSurface (#255)

Three unrelated subjects — a manifest's destinations, a deployment target's health, a toolchain's runtime — turn out to share one structure:

  • a declaration of intent, checkable offline;
  • a live state owned by somewhere else;
  • a reconciliation surface that may or may not exist yet;
  • and a failure mode where the offline check goes green and gets mistaken for the whole answer.

The canonical implementation of this shape now lives in @clossys/controller/conventions — it owns every rule this repository's tiers share and has no dependency of its own, so this package (which already depended on controller for GateResult) re-exports its copy rather than keeping a second one. Every name below is unchanged for a consumer of @clossys/builder: this is a consolidation of where the code lives, not a change to what it does. See @clossys/controller's own shipped documents, live-state-reconciliation.md for the shared document and routine-declaration.md / schedule-declaration.md for the two tiers that motivated generalizing this shape in the first place.

LiveStateSurfaceDeclaration is that declaration, with every field the two tiers that motivated this contract already used in practice:

import { validateLiveStateSurfaceDeclaration } from "@clossys/builder";

const findings = validateLiveStateSurfaceDeclaration({
  store: "the GitHub Actions branch-protection API",
  readableByScript: true, // explicit, never left implicit
  readableBy: "policy-drift.mjs",
  note: "A green run here is not evidence this is live — only policy-drift.mjs reading the real API says that.",
});

readableByScript: false requires reconciledBy instead of readableBy — what reconciles this subject when a script cannot read it at all. note is required either way, and validateLiveStateSurfaceDeclaration rejects a note that does not actually state the caveat: a green offline check is never evidence the declared thing is live.

The finding-kind vocabulary, all five

import { LIVE_STATE_SURFACE_FINDING_KINDS } from "@clossys/builder";
// [
//   "declared-but-not-live",
//   "live-but-not-declared",
//   "live-differs-from-declared",
//   "live-artifact-predates-its-declaration",
//   "declared-but-not-verifiable",
// ]

This is a finding vocabulary, not a verdict vocabulary — all five kinds can appear in a drifted report's findings list. The first four are outright disagreements a completed attempt found between declared and live state. The fifth, declared-but-not-verifiable, names one dimension of the comparison that could not be evaluated at all (a declaredAt/liveObservedAt that could not be parsed as an instant, for example), reported as its own finding precisely so it cannot silently discard a real disagreement already found alongside it. The same name is also, separately, the one declared reason an entire reconciliation attempt reports at the outcome level when nothing about the subject could be read at all: a reconciliation surface that cannot currently read live state is a declared gap with a named blocker, never a silent pass.

Three states, enforced in the types

reconcileLiveState returns exactly one of verified / drifted / could-not-verify — never a boolean, and never a fourth state. This reuses @clossys/controller/gates's GateResult ternary rather than inventing a fifth shape of the same idea inside this package. The could-not-verify constructor enforces the "named blocker" rule at construction time, not merely by convention:

import { liveStateCouldNotVerify, reconcileLiveState } from "@clossys/builder";

liveStateCouldNotVerify("deployment.web", ""); // throws — never a silent pass

const report = reconcileLiveState<string, string>({
  subject: "toolchain.runtime.node",
  declared: { value: "20.11.1" },
  observation: { attempted: true, live: "18.19.0" },
  agrees: (declared, live) => declared === live,
});
// report.result.verdict === "violated"
// report.result.findings[0].kind === "live-differs-from-declared"

Observation-bundle transport (#255, narrowed)

The fleet's evaluation model is inverted from a central scanner: each repository runs its own gates in its own CI and observes its own compliance; a plane wanting a fleet-wide picture reads what each repository already concluded about itself rather than re-scanning it centrally. Until now, those self-observations had no standard shape or transport — every plane that wanted one improvised its own.

#255 originally proposed generalizing that gap into a full declared-intent-vs-live-state framework. The owner narrowed that on purpose: an API without a consumer is a guess, and the reconciliation half of that shape already shipped as liveStateSurface above. What ships here is the transport — the one shape a repository's CI writes and a plane's CI reads — with everything else left for a real second consumer to motivate.

What this is not:

  • No network I/O anywhere in this package. Fetching a bundle from wherever a repository published it is the consuming plane's own job.
  • No storage opinion. A bundle can be a committed artifact, a release asset, or anything else a caller's CI decides — the contract is the shape, not where it lives.
  • No scheduling, no polling, and no further generalization of liveStateSurface. That remains #255's open remainder, filed as its own follow-up issue rather than guessed at here.

Writing one repository's observation

import { writeObservationBundle } from "@clossys/builder";

const serialized = writeObservationBundle({
  repository: { id: "example-org/example-app", ref: "a1b2c3d" },
  producedAt: new Date().toISOString(), // the CALLER's clock, never this package's
  gates: [
    { gateId: "secret-scan", result: { verdict: "satisfied", evaluated: 12 } },
    {
      gateId: "release-readiness",
      result: { verdict: "violated", findings: [{ rule: "version-not-bumped", severity: "high", message: "…" }] },
    },
  ],
});
// serialized is a JSON string -- write it wherever this repository's own CI
// decides an observation belongs.

writeObservationBundle is pure: caller-supplied data in, a serialized bundle out. It never reads a clock and never touches the network or the filesystem — producedAt is the caller's own timestamp, supplied explicitly, exactly like liveStateSurface's declaredAt/liveObservedAt above. Each gate's result reuses @clossys/controller/gates's GateResult ternary directly rather than a parallel shape — this package already depends on controller for it throughout.

Aggregating N repositories' observations

import { aggregateObservations } from "@clossys/builder";

// `bundles` is already-fetched data -- the plane's own CI did the fetching.
// This function never fetches anything itself.
const report = aggregateObservations({
  expectedRepositories: ["org/repo-a", "org/repo-b", "org/repo-c"],
  bundles: [bundleA, bundleB], // org/repo-c never showed up
  now: new Date().toISOString(),
  staleAfterMs: 24 * 60 * 60 * 1000, // 24h -- how old one CONTRIBUTING BUNDLE may be
  maxResultAgeMs: 60 * 60 * 1000, // 1h -- how old THIS RESULT may be before a later reader can no longer vouch for it
});

report.unobservedRepositories; // ["org/repo-c"] -- named, not a footnote
report.overall.verdict; // "indeterminate" -- an unobserved repository can never read as clean
report.computedAt; // echoes `now` -- carried so a persisted copy of `report` can be freshness-checked later

A repository this aggregation expected to hear from but did not, a bundle that fails validateObservationBundleShape, a bundle older than the caller-supplied staleAfterMs, and two or more bundles claiming the same repository identity are all reported indeterminate, with one of four named reasons — never omitted from report.repositories, and never resolved by picking one and discarding the rest ("last-write-wins"):

| Reason | Meaning | | --- | --- | | unobserved-repository | No bundle at all was received for this expected repository. | | invalid-bundle-schema | A bundle claiming this repository failed validateObservationBundleShape. | | duplicate-repository-identity | Two or more bundles claim the same repository. | | stale-observation | The bundle's producedAt is older than staleAfterMs relative to now. |

report.overall folds every report.repositories[].result with this package's own foldGateResults (@clossys/controller/gates), whose documented precedence — indeterminate beats violated beats satisfied — is exactly what keeps "2 of 5 repositories were unobserved" from silently reading as "the 3 we heard from were clean, so we're done." report.overall.verdict is "satisfied" only when every expected repository was cleanly, freshly, and uniquely observed and every one of its own gates was itself satisfied.

report.receivedCount (bundles that passed schema validation, independent of whether they matched an expected repository) and report.unexpectedRepositories (repository ids present in bundles but never named in expectedRepositories) are reported for the same reason: nothing this function is handed is silently dropped from the report, even data that turned out not to matter for the verdict.

Close condition: this loop closes for a plane aggregating per-repository bundles when three predicates all hold: every id in expectedRepositories has exactly one entry in report.repositories; every entry's classification is observed (the four gap classifications — unobserved-repository, invalid-bundle-schema, duplicate-repository-identity, stale-observation — are each indeterminate by construction and therefore never part of a close); every observed entry's result.verdict is satisfied; and report.unattributedCount is zero — a bundle that arrived but could not be tied to any repository id is evidence that was never evaluated, and it folds into overall as unattributed-bundle (indeterminate), so it can never coexist with a close. report.overall.verdict then reads satisfied because all three held, never because the aggregator simply stopped looking. A repository that never reports in must show up as a named gap, not as a shorter list. The loop reopens on any bundle shape aggregateObservations cannot sort into one of the four gap classifications above: that is a transport-contract gap, filed the same way #255's narrowed-off remainder (scheduling, polling, further generalization of liveStateSurface) already is, rather than patched ad hoc inside this package.

The aggregate's own age is a different question from any bundle's (#340)

stale-observation above answers "is any ONE contributing bundle too old to fold in." It cannot answer "is this AGGREGATION ITSELF too old to still be presented as current" — a plane that only re-runs this aggregation on a push to the repository that hosts it can go a long time between runs, and every one of its inputs can change in the meantime with nothing re-evaluating the aggregate to notice. aggregateObservations cannot detect that about its own output: report.computedAt always equals the now it was just called with, so no check inside that one call can ever observe its own result aging after the fact.

That is why report also carries computedAt and maxResultAgeMs — so that a caller who persists report (a committed artifact, a status check, anything a different process reads later) can hand both back to checkObservationAggregateFreshness, alongside a FRESH now supplied at read time, to ask the question aggregateObservations cannot ask of its own output:

import { checkObservationAggregateFreshness } from "@clossys/builder";

// `storedReport` was read back from wherever the previous run persisted it --
// not produced by the same call as the freshness check below.
const freshness = checkObservationAggregateFreshness({
  computedAt: storedReport.computedAt,
  maxResultAgeMs: storedReport.maxResultAgeMs,
  now: new Date().toISOString(), // the reader's own clock, at READ time
});

// A stale but otherwise-unanimous-pass stored report must never silently
// stay satisfied: fold the stored verdict together with the freshness check
// through any GateResult fold, including this package's own dependency,
// `@clossys/controller/gates`'s `foldGateResults` -- the same
// combinator `aggregateObservations` already uses internally.
const effective = freshness.verdict === "satisfied" ? storedReport.overall : freshness;

checkObservationAggregateFreshness reports indeterminate with reason stale-aggregate-result — never a restated stale-observation, because the two questions are different: stale-observation is about one bundle's age at aggregation time; stale-aggregate-result is about the aggregate's own age at whatever later moment it is actually read. A schedule (or any other input-driven trigger) makes this staleness less likely; this check is what makes it detectable, because it is the one thing that still works when that trigger has silently stopped firing.

Or from the shell, once built — the same builder-verify-toolchain bin verifyToolchain uses, via its aggregate-observations and check-observation-freshness subcommands:

npx builder-verify-toolchain aggregate-observations --inputs ./aggregate-observations-inputs.json
npx builder-verify-toolchain check-observation-freshness --inputs ./check-observation-freshness-inputs.json
# Exit codes for both: 0 = satisfied, 1 = violated, 2 = indeterminate/could not run.

Manifest engine (formerly @example/provisioning)

An idempotent engine for applying a provisioning manifest to a machine, and for checking afterwards whether the machine still agrees with it. The engine is neutral machinery: it applies manifests and owns none, and it has no opinion about what belongs in one.

import {
  applyInstallation,
  createNodeFileSystem,
  createRuntimeContext,
  loadManifest,
  planInstallation,
  verifyInstallation,
} from "@clossys/builder";

const manifest = loadManifest(JSON.parse(rawManifestJson));
const runtime = createRuntimeContext(manifest, { home: os.homedir(), sourceRoot: myCanonicalCheckout });

// Pure. Touches no filesystem, so it can be printed or reviewed first.
const plan = planInstallation(manifest, runtime);

const fs = createNodeFileSystem();
const result = applyInstallation(plan, fs, { backupRoot: `${runtime.home}/.config-backups/${stamp}` });

// Later, or in a drift check: reads the machine, not the manifest.
const findings = verifyInstallation(plan, fs);

links, copies, managedBlocks, and privateDirectories are the four manifest collections; expandTokens resolves ${HOME}, ${SOURCE_ROOT}, ${WORKSPACE_ROOT}, and any extraTokens a caller supplies. composeManagedBlock, renderManagedBlock, withoutManagedBlock, and hasExactlyOneBlock operate on the marker-delimited region a managed block owns inside a file this engine does not otherwise own.

Safety properties, unchanged from provisioning: nothing is overwritten before it is backed up (ApplyOptions.backupRoot is required); private directories are handled first; a links entry declared with target (a chained link) applies last, after copies and managed blocks; a same-content symlink at a copy destination is replaced rather than accepted; ambiguous managed-block markers are refused, not guessed; applying throws, verifying reports.

Multi-source composition (#240)

The engine above assumes one sourceRoot and one plan. That is unchanged and every existing single-source caller keeps working exactly as written above — this section covers a separate, additive layer for a caller that needs to compose one installation plan out of several independently-owned sources, e.g. several account-owned workspace checkouts, without staging them under one repository authority.

Build one Plan per source with the same createRuntimeContext / planInstallation shown above — nothing new there — then tag each with the identifier it asked to be known by and compose them:

import {
  applyComposedInstallation,
  composeInstallationPlans,
  createRuntimeContext,
  createNodeFileSystem,
  loadManifest,
  planInstallation,
  verifyComposedInstallation,
} from "@clossys/builder";

const namedPlans = sources.map(({ name, sourceRoot, manifest }) => ({
  source: name,
  plan: planInstallation(manifest, createRuntimeContext(manifest, { home, sourceRoot })),
}));

// Pure, like planInstallation. Every operation carries the source that
// requested it; throws DestinationCollisionError, naming every contributing
// source, if two sources claim the same destination.
const composed = composeInstallationPlans(namedPlans);

const fs = createNodeFileSystem();
const result = applyComposedInstallation(namedPlans, fs, { backupRoot });

const findings = verifyComposedInstallation(namedPlans, fs);
// findings[i].source names which source's plan the finding came from.

composeInstallationPlans validates the full composition — including collision detection — before applyComposedInstallation makes a single filesystem call, so a colliding destination is never discovered halfway through an apply; nothing from either contributing source is written. Collision detection covers links, copies, and managedBlocks destinations only. Two sources asking for the same privateDirectories entry to exist is not a collision — creating (or leaving alone) a directory with a fixed mode is idempotent and non-destructive regardless of which source asks or how many do, the same line loadManifest already draws for one manifest's own destinations.

applyComposedInstallation applies each source's plan through the unchanged applyInstallation, in the order namedPlans supplies, sharing one backupRoot. A known limit, not an oversight: a chained link (a links entry declared with target) or a copy/managed-block destination nested inside another source's private directory only resolves correctly if the source it depends on was already applied — this package does not detect or order around a cross-source dependency, because doing so would mean inventing a precedence policy across sources, which is exactly what this package must not do (see "What this does not do" below). Supply namedPlans in an order that satisfies any real cross-source dependency, or avoid creating one.

What this does not do, and why: composing several sources' plans is the only thing this module does. It never discovers what those sources are — that is a caller's job, informed by whatever registry or configuration names its account-owned workspace repositories, never this package's. It never decides which source wins a conflicting destination — a collision is always refused, never resolved, because resolving it is a precedence and policy decision this package does not make (see #240's Boundaries: no account discovery, no precedence or conflict-resolution policy, inside provisioning or its successor here).

This first increment covers three of #240's eight acceptance criteria — multiple named sources with a preserved single-source compatibility path, per-operation provenance, and pre-mutation collision detection with evidence. It deliberately does not cover the other five: a durable applied receipt (so a later run can compare against what actually got installed, not just against the current plan); retirement planning for a destination a prior plan owned that a newer plan no longer names; the clean-room adapter qualification; and account discovery itself, which is out of scope for this package by design (see "What this does not do" above), not merely undelivered. See the pull request that introduced this section for the full breakdown.

A note on vocabulary: this module does not report through liveStateSurface's declared/verified/could-not-verify vocabulary described above. That contract reconciles one declaration against one live read of a subject somewhere else; a destination collision is neither — it is two declared plans disagreeing with each other, discovered before either is applied and before there is any live state to read at all. The one place this module does read live state — verifyComposedInstallation — delegates to the unchanged verifyInstallation, which already reports through the pre-existing Finding vocabulary rather than liveStateSurface; changing that return shape is a breaking change to already-published behavior and is out of scope here.

Machine composition (./machine subpath, #393)

Composing one machine's skill tree from several account-owned workspace checkouts plus a third-party-scoped skill source — the mechanism a retiring account-owned installer repository used to provide, promoted into this package once that repository had nothing left to place. Two decisions were made explicitly rather than by default, and both are recorded here because "the package that already holds the files" and "the package that owns machines" were both defensible, and only one shape of composition actually worked with what this package already ships.

Decision 1: builder owns the mechanism, not controller

@clossys/controller/conventions already ships the account-neutral, package-owned content itself — five documents plus a managed shell block — and #393 leaves every one of those files exactly where they are, untouched. What is missing is not content; it is a mechanism to place content and per-account skill trees onto a machine, and roughly 90% of that mechanism already lived in this package before #393: composeInstallationPlans, applyInstallation, verifyInstallation, and GateResult are all builder's own. Building the machine installer inside controller would have meant duplicating composition.ts in a package that has no reason to depend on the filesystem-mutation half of this one. Builder's own charter — "declared -> actual: toolchain, pipeline shape, machines, platforms" — already names "machines" as a first-class subject; controller's does not. So: builder owns the mechanism, controller keeps owning the content. A caller composing a real machine imports package-owned entries from controller's CONVENTION_DOCUMENTS / CONVENTION_ADAPTERS catalog (@clossys/ controller/conventions) as one more source alongside the account and third-party sources this subpath discovers — this package does not fold that catalog into a manifest itself, because doing so would mean hard-coding one particular machine's destination paths into a public, account-neutral package. That composition — which document goes to which absolute path on which real machine — is local machine state, never checked in here.

Decision 2: composition is union by directory link, not per-skill links or a copy

~/.agents/skills (or wherever a caller composes skills to) must present the union of several accounts' skill trees plus a third-party tree. This decision was made twice. The first pass (documented in this package's history) adopted per-skill links; the owner overturned that on 2026-09-21 (#393) in favor of union by directory link, recorded below. Three shapes were on the table:

  • A single directory symlink — the mechanism this replaces used. Rejected: a symlink can only point at one source, so it cannot union N accounts' trees no matter how many are discovered.

  • A materialized copy of every account's skills into one directory. Rejected: a copy drifts from its source the moment the source changes, and this package's own manifest engine already treats "canonical elsewhere" as link-not-copy everywhere else it applies (see links vs. copies in ../types.ts) — inventing a copy-based exception here for skills alone would be a second policy this package does not need.

  • Union by directory link. Adopted. Each discovered source (./discovery.ts's account workspaces, ./third-party.ts's vendored skills) becomes one named Plan carrying exactly ONE links entry — the whole source tree, linked as a single directory into <composedSkillsRoot>/<sourceName> (./skills-manifest.ts's buildSkillsManifest) — never one entry per skill. sourceName is the same identifier the source already composes under (an account's declared account, or the literal "third-party"), so composedSkillsRoot's immediate children are always attributable to exactly one source at a glance.

    This was NOT the first shape tried, and the reversal has a real cost worth naming: per-skill links let composeInstallationPlans's EXISTING per-destination collision check catch a same-named skill across two sources as a side effect of link creation, for free. Directory-linking gives up that side effect — two sources' directory links never share a literal destination, by design, so the built-in check no longer sees a same-named-skill collision at all. ./skills-manifest.ts's detectSkillNameCollisions closes that gap explicitly: it enumerates the skill names in every source tree and compares them BEFORE any link is planned, returning ../composition.ts's own DestinationCollision shape — never a second collision vocabulary — so report.ts folds a skill-name collision into a report exactly the way it already folds a real DestinationCollisionError from composeInstallationPlans itself (a class-one destination colliding with an account's directory link, for instance — see the "destination collisions" test coverage for verifyMachine, which does not ship). Two accounts shipping a skill with the same name is still a reported conflict, never a silent last-writer-wins; it is just no longer free.

Class 1: package-owned, account-neutral conventions, and where its destination map lives (#410)

#393 named three classes of source; the two above are classes 2 and 3. Class 1 — machine guidance, agent policy rules, shell integration, command hooks — is not new content. It already ships in @clossys/controller/conventions's CONVENTION_DOCUMENTS and CONVENTION_ADAPTERS catalogs, and the retiring account repository was only ever the placement mechanism for it. What was missing was not the content; it was where the mapping from that content to real machine destinations lives, and #410 exists because three of the four candidate homes fail:

  1. Inside builder itself — every consumer of a general-purpose package would inherit one particular machine's opinion about where its own files go. A composition engine is general; a specific machine's destination map is not.
  2. Inside a surviving account plane — this breaks the rule the whole design rests on: no account plane may reach into another's, and only the machine layer sees all of them. Declaring the machine's destination map from inside one account plane is reaching, by definition.
  3. Inside the retiring repository — the status quo being retired, listed only to be ruled out explicitly.
  4. On the machine, in no repository at all. A machine-local declaration, owned by the machine layer, versioned nowhere because it is not a shared artifact. This is the one adopted.

./machine-layer.ts's MachineLayerDeclaration is that declaration: a plain JSON document, read from a path the caller supplies or from BUILDER_MACHINE_LAYER_DECLARATION_PATH, naming — for each catalog id it places — an install kind (link, copy, or managed-block) and a destination expressed relative to home, never absolute. home is already an explicit input verifyMachine never infers (MachineVerifyInputs.home), so there was never a real "absolute paths baked into a public package" problem to solve by keeping this out of builder — that reasoning, floated early in #393's history, was the wrong one and #410 explicitly retires it. The actual reason class 1's map stays out of this package is ownership: a general engine should not ship one machine's opinion.

Because the declaration is a plain local file — never a path inside a repository, never fetched over the network — reading it needs no credential of any kind, for any repository. That is a stronger property than packages/observer's coverage-declaration.ts contract achieves for its own different problem (a repository-hosted declaration reachable by an unauthenticated raw-content GET): there is no repository here to need unauthenticated access to in the first place.

buildClassOneManifest is catalog-aware and throws on an id the catalog does not ship, or on templated content (a document or adapter carrying ${TOKEN} placeholders) declared "link" — a symlinked reader would receive the literal token. loadClassOnePolicy is the public entry point and never throws, following packages/integrator's detectSupersession split exactly: every internal failure folds into a named indeterminate reason, including an empty destinations array — nothing declared is not evidence class 1 has been composed (#338).

verifyMachine's class-one source is optional the same way third-party is: omitting classOneDeclarationPath and the environment variable means "this run does not compose class 1," not a failure — but composed, once supplied, it goes through the exact same composeInstallationPlans classes 2 and 3 already use, tagged "package-conventions", so a destination it shares with an account workspace or a third-party skill is a reported DestinationCollisionError like any other, never a silent last-writer-wins.

The single-directory-symlink to per-source-directory-links transition (#240)

On the machine this replaces, ~/.agents/skills (composedSkillsRoot) is today a single directory symlink into the repository being retired. This subpath's own composed shape is one directory link per source (decision 2, above) — many links, each scoped under a real, non-symlinked composedSkillsRoot, rather than one symlink standing in for the whole union. #240's own reproduction records the naive transition crashing applyInstallation on a stale dangling link: the old symlink no longer resolves, and the generic engine's own recursive mkdir (called while preparing to write the first source's directory link) throws an opaque, unrelated ENOENT deep inside apply.ts's replace() — never a clear, actionable finding. The non-dangling case is just as unsafe in the other direction: a symlink still pointing at a directory that still exists lets mkdir walk straight through it, silently writing the new directory link INSIDE the old repository's tree instead of into a real, machine-owned directory.

./skills-manifest.ts's buildSkillsManifest closes both cases by declaring composedSkillsRoot itself as a privateDirectories entry (create: true) on every source's manifest, ahead of that source's own single directory links entry — no new engine mechanism, reusing the existing safety apply.ts's applyPrivateDirectory and verify.ts's verifyPrivateDirectory already enforce for every other private directory this engine manages: a destination that is a symlink (dangling or not) is refused with a named, actionable error before anything is touched, and multiple sources declaring the identical private directory is not a collision — composeInstallationPlans already exempts private-directory operations from collision detection (see "Multi-source composition," above), which is exactly the shape needed here since every account, third-party, and class-one source shares the one composed root. This refusal stays scoped to composedSkillsRoot ITSELF — never to its children, where a directory symlink per source (composedSkillsRoot/<sourceName>) is now the entire, intended output. ./skills-manifest.test.ts reproduces the exact transition #240 recorded — a dangling directory symlink at composedSkillsRoot, replaced by per-source directory links — and asserts it fails with a named error, leaves the stale symlink completely untouched, and never crashes.

Explicit retirement of a dropped destination (#240)

When a source stops contributing a destination it used to manage — a workspace disappears entirely because its repository was deleted, the third-party root is unconfigured, a class-one convention id is dropped from the machine layer declaration — that destination must be retired explicitly, never left silently orphaned on the machine. Under union-by-directory-link (decision 2, above), an account workspace losing an individual skill is NOT one of these triggers: the account's own destination (composedSkillsRoot/<account>) is the whole source tree's directory link, unaffected by which skills currently live inside it, so retirement here is a statement about SOURCES disappearing, never about individual skills. ../composition.ts's diffRetiredDestinations is the pure comparison this needs: given the destinations a PRIOR run's composition managed and the CURRENT run's actual composed operations, it returns a RetirementReport naming every destination (RetiredDestination, one entry per destination-path/source/kind triple) the prior run owned that no current source claims at all. private-directory operations are excluded from the comparison in both directions, the identical reasoning composeInstallationPlans already applies to its own collision check (a directory several sources want to exist is not "owned" by any one of them).

This never deletes anything — reporting is the entire contract, and a caller wanting destructive cleanup must build that on top, explicitly, as its own opt-in step; this module will never do it by default, the same discipline applyInstallation already keeps for a destination a manifest simply stops mentioning. verifyMachine wires this in as an entirely optional row: supplying MachineVerifyInputs.previousCompositionPath (a JSON document shaped {"schemaVersion":1,"operations":[...]}, typically a prior run's own composeInstallationPlans(...).operations persisted verbatim — this module never writes that file itself) reports every retired destination as a machine/destination-retired finding; omitting it means "this run does not check for retired destinations," not an error. A retirement check against composition that itself did not resolve is reported indeterminate rather than diffed against a partial machine.

Discovery: never a hard-coded list, never a silent partial machine

./discovery.ts's discoverAccountWorkspaces takes a root from the caller or the BUILDER_MACHINE_WORKSPACES_ROOT environment variable — never a guessed default, so no absolute path to any account repository appears anywhere in this package's content. A directory under that root is a candidate only once it has placed a builder-machine-workspace.json marker at its own root, declaring its account identifier and its skillsPath — the same discovery-by-self-declaration shape npm's own workspace globbing uses. A directory with no marker is simply not a workspace, excluded the same way an npm workspace glob excludes a directory with no package.json. A directory with a marker has declared intent to be counted, and from that point every failure — an unreadable marker, malformed JSON, a schema mismatch, an unreadable declared skill tree, two workspaces claiming the same account — is indeterminate, always present in the result, never dropped. ./third-party.ts's loadThirdPartySkills is the third-party-scoped mirror of the same ternary, keyed off SkillScope's own "third-party" value (@clossys/controller/conventions) rather than a second vocabulary — THIRD_PARTY_SCOPE's assignment stops compiling if that union ever drops the value.

./report.ts's verifyMachine (behind builder-verify-machine, ./cli.ts + ./bin.ts — a SEPARATE compiled entry file from ../ci/bin.js, since this repository invokes gates by direct dist path and a CLI that dispatches on the invoked bin name is unreachable that way) is the orchestrator that makes the whole-machine rule concrete: if ANY discovered source is indeterminate, composition itself is skipped and reported indeterminate — never a partially-composed machine reported as satisfied because the sources that DID resolve happened to verify clean.

import { verifyMachine, createNodeDiscoveryPort } from "@clossys/builder/machine";
import { createNodeFileSystem } from "@clossys/builder";

const report = verifyMachine(createNodeDiscoveryPort(), createNodeFileSystem(), {
  schemaVersion: 1,
  home: process.env.HOME,
  composedSkillsRoot: `${process.env.HOME}/.agents/skills`,
  // accountWorkspacesRoot / thirdPartySkillsRoot / classOneDeclarationPath may
  // come from here instead of BUILDER_MACHINE_WORKSPACES_ROOT /
  // BUILDER_MACHINE_THIRD_PARTY_SKILLS_ROOT / BUILDER_MACHINE_LAYER_DECLARATION_PATH.
});
// report.overall.verdict: "satisfied" | "violated" | "indeterminate"
// report.exitCode: 0 | 1 | 2 — the same fold as everywhere else in this package
npx builder-verify-machine --inputs machine-verify-inputs.json
# Exit codes: 0 = every managed skill destination resolves as composed,
#             1 = at least one destination or source disagreed,
#             2 = could not verify (an unresolvable source, or bad input).

Deployment (./deployment subpath, formerly @example/deployment)

Dependency-free deployment surface contracts, configuration planning, health evaluation, and read-only Vercel and Render adapters — preserved as a subpath rather than flattened into the root entrypoint, so its own export shape (a root plus two provider subpaths) stays intact.

import {
  defineDeploymentManifest,
  evaluateDeploymentHealth,
  validateDeploymentManifest,
} from "@clossys/builder/deployment";
import { createVercelInspector, renderVercelConfiguration } from "@clossys/builder/deployment/vercel";
import { createRenderInspector, renderRenderConfiguration } from "@clossys/builder/deployment/render";

const manifest = defineDeploymentManifest({
  schemaVersion: "1",
  surfaces: [{ id: "web", provider: "vercel", environment: "production", health: { kind: "http", url: "https://example.com/health" } }],
});

if (validateDeploymentManifest(manifest).length > 0) throw new Error("Invalid manifest");

const health = evaluateDeploymentHealth([{ surfaceId: "web", status: "healthy" }]);

The provider adapters make GET requests only, obtain a bearer token from a caller-injected provider at inspection time, and never read a secret store or process environment. See the module doc comments under src/deployment/ for the full contract each subpath ships — the shape is unchanged from @example/deployment's own README, which this package's history carries forward.

Which branch feeds which environment (issue #929)

DEPLOYMENT_ENVIRONMENTS enumerates the environments and stops. A repository that takes day-to-day work on one long-lived branch and deploys from a second one is describing a real, checkable arrangement, and until now no part of this contract could hold it: a surface said where it is live and how to tell, never what feeds it.

import {
  checkDeploymentBranchBindings,
  defineDeploymentBranchBindings,
  validateDeploymentBranchBindings,
} from "@clossys/builder/deployment";

const branchBindings = defineDeploymentBranchBindings([
  { environment: "production", branch: "release" },
  { environment: "preview", branch: "main" },
]);

const result = checkDeploymentBranchBindings({ manifest, branchBindings });
// => { ok: true, findings: [], surfacesChecked: 2, bindingsChecked: 2, productionBranch: "release" }

branch is a plain string. It names a branch that a repository's own profile — a different package's contract entirely — declares, and this package does not import that package to resolve it. That is the same seam @clossys/strategist's BrandDerivation already uses for token slots and voice rules, and for the same reason: one package declares the topology, this one declares which environment consumes it, and the repository that depends on both is the one place the two names can actually be compared.

This stays a contract a repository satisfies. Nothing here deploys, mutates a provider, or makes a network call; the provider adapters remain read-only.

checkDeploymentBranchBindings fails closed. An empty binding list or a manifest with no surfaces is a finding, never a vacuous pass, and surfacesChecked/bindingsChecked are present in every result so "nothing was compared" and "everything agreed" can never be told apart by a caller who only glances at ok. productionBranch appears only on a clean result.

Provider-verified deploy record (issues #1518, #360)

A deployment surface can declare production branch, preview branches, the standard preview URL, protection mode (sso or none), and required environment names per target (preview or production), each marked secret or plain and project or shared. Optional releaseRef: true names the release-ref model: production is the release branch and main is a preview branch.

verifyDeployRecord(declared, observed) compares that declaration to a caller-supplied provider observation (fixture or read-only adapter output). The observation carries names and settings only — never secret values — and must also name productionCommit, builtCommit, and publicCommit as 40-hex SHAs. The verifier is satisfied only when all three are present, equal, and the rest of the declaration matches; it reports deploy-observation-incomplete when any commit field is missing or invalid, deploy-provider-build-mismatch when the provider build SHA differs from production, and deploy-public-cache-stale when the public page serves a different SHA. It reports indeterminate when an observation row includes a value field (including on commit fields).

planProductionRefUpdate({ productionIsAncestor }) returns the required production-ref advance (fast-forward or a single merge commit with production as first parent and integration as second). No git I/O. verifyProductionRefUpdate(planned, observed) rejects force and admin-bypass updates and reconciles fast-forward and merge observations against that plan.

import { verifyDeployRecord } from "@clossys/builder/deployment";

const result = verifyDeployRecord(declared, observed);
// => { verdict: "satisfied", evaluated: n } | { verdict: "violated", findings: [...] }
//    | { verdict: "indeterminate", reason: "observation-carries-secret-value", ... }

| Export | Kind | Purpose | | --- | --- | --- | | verifyDeployRecord(declared, observed) | function | Reconciles declared deploy settings and production/built/public commit SHAs against a provider observation; fails on branch, preview URL, environment, protection, build, and cache drift. | | planProductionRefUpdate(input) / verifyProductionRefUpdate(planned, observed) | functions | Plan and verify fast-forward or merge production-ref updates without git I/O. | | DEPLOY_RECORD_INDETERMINATE_REASONS / PRODUCTION_REF_UPDATE_INDETERMINATE_REASONS | constants | Declared indeterminate vocabulary for deploy-record and production-ref verification. | | DeployRecordDefinition / DeployRecordObservation / related types | types | Declared and observed deploy-record shapes (names and settings only). |

| Export | Kind | Purpose | | --- | --- | --- | | validateDeploymentBranchBindings(value) / isValidDeploymentBranchBindings(value) | functions | Strict structural validation of untyped authoring input; a throwing accessor reads as unreadable input, never as a pass. | | defineDeploymentBranchBindings(definitions) | function | Produces a detached, explicit binding list. | | checkDeploymentBranchBindings(input) | function | Compares a manifest's declared environments against the bindings; reports every unbound environment and an unbound production. | | REQUIRED_BOUND_DEPLOYMENT_ENVIRONMENT | constant | The one environment a repository cannot leave unbound once a surface declares it. | | DeploymentBranchBinding / DeploymentBranchBindingDefinition / DeploymentBranchBindingCheck / DeploymentBranchBindingFindingRule | types | The binding, its authoring shape, the check result, and the closed finding vocabulary. |

evaluateDeploymentHealth is also reachable from the shell, via the same builder-verify-toolchain bin's deployment-health subcommand:

npx builder-verify-toolchain deployment-health --inputs ./deployment-health-inputs.json
# --inputs is { "observations": [{ "surfaceId": "web", "status": "healthy" }, ...] }.
# Exit codes: 0 = every recognized observation is healthy, 1 = at least one is
#             degraded or unhealthy, 2 = could not evaluate (bad input, or no
#             observation could be recognized at all).

Web deployment surface: domain, DNS, and hosting (./deployment/domain subpath, #1211)

The web surface a product actually serves — one domain, its DNS records, one hosting project, and the environment-to-branch mapping that feeds it — is declared once and checked against live reality. Provider-neutral, with Cloudflare (DNS) and Vercel (hosting) as the first worked example; see vercel-hosting.ts's own header for the one place that adapter is named.

This follows #1187's governing "who does what" split exactly: this subpath defines and verifies. It never deploys, never calls a provider's write API, and never reads or stores a token — DNS resolution, the TLS handshake, and an HTTP request are all public, credential-free operations by construction. A "right deployment serving" check needs a provider credential (Vercel's own API), so that one dimension is never performed by this subpath directly: a caller runs createVercelInspector (./deployment/vercel) with its own injected bearer token, and folds the result in through observeVercelHosting.

import {
  createNodeDnsResolver,
  createNodeTlsProbe,
  defineWebSurfaceDeclaration,
  renderWebSurfaceSetupSteps,
  verifyWebSurfaceLiveState,
} from "@clossys/builder/deployment/domain";

const declaration = defineWebSurfaceDeclaration({
  schemaVersion: "1",
  domain: "example.com",
  dnsProvider: "cloudflare",
  records: [
    { type: "A", name: "@", value: "192.0.2.1" },
    { type: "CNAME", name: "www", value: "example.com", proxied: true },
  ],
  hostingProvider: "vercel",
  hostingProject: "example-site",
  build: { command: "npm run build", outputDirectory: ".next", applicationRoot: "apps/site" },
  environments: [{ environment: "production", hostname: "example.com", branch: "main" }],
  routes: ["/about"],
});

// The exact configuration a coding agent reviews and applies through the
// provider CLIs, with the human's approval. Deterministic; no I/O.
for (const step of renderWebSurfaceSetupSteps(declaration)) console.log(step);

// Live verification: read-only, and needs no provider token for DNS, TLS, or HTTP.
void (async () => {
  const report = await verifyWebSurfaceLiveState(declaration, {
    resolveDns: createNodeDnsResolver(),
    probeTls: createNodeTlsProbe(),
    fetch,
  });
  console.log(report.overall.verdict); // "satisfied" | "violated" | "indeterminate"
})();

Every one of the four checks is independent and every one degrades to indeterminate — never satisfied — when it could not reach what it was checking (offline, DNS timeout, an unreachable host): see #914. report.overall is satisfied only when DNS, TLS, HTTP, and a supplied hosting observation all are; omitting hostingObservation reports hosting (and therefore overall) indeterminate with reason "hosting-not-configured" rather than silently dropping that dimension from the result.

A proxied DNS record (Cloudflare's orange-cloud, or any provider that terminates traffic at its own edge) is checked only for resolving at all — its live value is the provider's own edge address, never the declared origin, so an exact match is never asserted for one.

| Export | Kind | Purpose | | --- | --- | --- | | defineWebSurfaceDeclaration(definition) / validateWebSurfaceDeclaration(value) / isValidWebSurfaceDeclaration(value) | functions | Structural validation and a detached, defaulted declaration — the same define/validate split the rest of ./deployment already holds. | | normalizeWebSurfaceDeclaration(declaration) / serializeWebSurfaceDeclaration(declaration) | functions | A stable, diffable sort order and its JSON serialization. | | renderWebSurfaceSetupSteps(declaration) | function | The exact, deterministic configuration steps a coding agent reviews and applies. Zero I/O. | | checkDnsRecords(declaration, ports) / checkTlsCertificate(hostnames, ports) / checkRoutes(hostname, routes, ports) | functions | The three credential-free live checks, each returning a GateResult (@clossys/controller/gates). | | createNodeDnsResolver() / createNodeTlsProbe() | functions | The real node:dns / node:tls adapters a caller wires into the checks above. | | observeVercelHosting(inspection, input) | function | Folds a createVercelInspector(...).inspect(...) result into the "right deployment serving" dimension. Pure; touches no token. | | verifyWebSurfaceLiveState(declaration, ports) | function | The aggregate report — dns, tls, http, hosting, and overall. | | DnsRecord / DnsRecordType / DNS_RECORD_TYPES / WebSurfaceDeclaration / WebSurfaceEnvironment / WebSurfaceBuildSettings | types | The provider-neutral declaration schema. | | WebSurfaceLiveVerificationReport / WebSurfaceVerificationPorts | types | The live-check aggregate and the ports it takes. |

Hosting commands and local dev ports (#1526, #1529)

A surface's hosting config calls two package commands. The Vercel adapter names them:

import { vercelHostingCommands } from "@clossys/builder/deployment/vercel";

vercelHostingCommands("web");
// installCommand: npx @clossys/builder@<version> hosting install --surface web
// ignoreCommand: npx @clossys/builder@<version> hosting should-build --surface web

renderVercelConfiguration writes those commands into vercel.json as installCommand and ignoreCommand.

builder hosting install --surface <id> reads builder.hosting.json. For each private scope it checks that the repository route file sends that scope to the declared registry, then runs npm ci. A rejected credential, a missing credential, and a mis-routed scope are different errors, and each names the scope. The command's output does not include the credential. The credential is written to a user-level config outside the repository and that file is removed when the command exits. The install's child environment does not receive the credential value or VERCEL_TOKEN.

The build environment is checked by name. A declared name is present or absent. VERCEL_TOKEN is not placed in that build environment.

{
  "schemaVersion": "1",
  "surfaces": [{
    "id": "web",
    "inputs": ["apps/web"],
    "privateScopes": [{
      "scope": "@example",
      "registry": "https://npm.example.test",
      "credentialVariable": "EXAMPLE_REGISTRY_TOKEN"
    }],
    "buildEnvironment": ["EMAIL_PROVIDER_KEY"]
  }]
}

The route file line for that scope is @example:registry=https://npm.example.test.

builder hosting should-build --surface <id> decides from the surface's declared inputs plus the files the hosting commands reference, including the install command's own file. When that file is the only change, the command prints changed: src/hosting/install.ts and exits 1. Exit 0 means no relevant input changed. Exit 1 is the exit Vercel's ignore command treats as "continue the build."

A deployment surface record can declare localPort. The dev script is checked against that port: it must pass the same port with --port or -p.

import { defineDeploymentManifest, validateDeploymentManifest } from "@clossys/builder/deployment";

const manifest = defineDeploymentManifest({
  schemaVersion: "1",
  surfaces: [{
    id: "web",
    provider: "vercel",
    environment: "development",
    localPort: 4321,
    devScript: "next dev --port 4321",
    health: { kind: "http", url: "https://example.test/health" },
  }],
});

validateDeploymentManifest(manifest);
builder hosting install --surface web
builder hosting should-build --surface web

Toolchain

The runtime pin, the package-manager pin, and the build order, expressed and reconciled as the same liveStateSurface statement everything else in this package makes. A pin is an exact value — never a range; see src/toolchain.ts for why guessing at range syntax this module does not parse would be worse than refusing to.

import { reconcileToolchain, validateToolchainDeclaration } from "@clossys/builder";

const declaration = {
  runtime: { name: "node", version: "20.11.1" },
  packageManager: { name: "npm", version: "10.5.0" },
  buildOrder: { packages: ["core", "adapters", "app"] },
};

if (validateToolchainDeclaration(declaration).length > 0) throw new Error("Invalid toolchain declaration");

const [runtime, packageManager, buildOrder] = reconcileToolchain(declaration, {
  runtime: { attempted: true, live: process.version },
  packageManager: { attempted: true, live: "10.5.0" },
  buildOrder: { attempted: true, live: declaration.buildOrder.packages },
});

This package never reads process.version, spawns a shell, or reads a root manifest itself — every observation is supplied by the caller. A function that read its own runtime could never be tested for the disagreeing case without actually running on a disagreeing machine.

Shared CI gate mechanics (#257)

The same secret-scan-shaped CI gate had been built independently at least four times across this account family with no channel between the copies — a fix discovered in one copy had no way to reach the other three. #257 asks for that mechanism to live in one place, shipped as importable machinery a consumer's own thin workflow invokes — the shape @clossys/inspector now provides — rather than as a cross-repository reusable workflow reference. A uses: pointing at another account's workflow or action is not the sanctioned exception this account family's cross-account boundary rule allows; a downstream package depending on an upstream package is, and that is what this subpath ships.

./ci holds the shared half — a fold from any number of LiveStateSubjectReport values into one 0/1/2 exit-code verdict (foldLiveStateReports), plus a minimum-safe-version staleness floor (checkVersionFloor) that gives a caller running a pre-fix build the same "you are behind" signal @clossys/inspector provides, not merely a changelog entry someone may or may not read — and the concrete gate built on both: builder-verify-toolchain, an installed CLI a consuming repository's own thin GitHub Actions workflow runs. See documents/caller-workflow.md for that workflow in full, including why the decisive command must never sit on the left side of a pipe under GitHub Actions' bash -e {0} (pipefail unset) and why "can't run here" must become an exit 2, never an if: skip — a skipped required check reports to a merge gate as satisfied, not absent.

npx builder-verify-toolchain --inputs verify-toolchain-inputs.json
# Exit codes: 0 = verified, 1 = drifted, 2 = could not verify.

The same fold and floor are also importable directly, for a future gate over a different subject that wants this package's mechanics without shelling out to the installed binary:

import { checkVersionFloor, foldLiveStateReports } from "@clossys/builder/ci";

Every check here is hermetic: src/ci/*.test.ts injects every declaration, observation, and file read, and calls no network and no real machine.

Metric

Drift: the count of subjects where declared and actual disagree, plus, reported separately, the count that could not be verified at all. Neither is folded into the other — a build that reports zero drift by refusing to look at half its subjects has not reported zero drift.

Loop

  • aim — every declared subject (a manifest destination, a deployment surface, a toolchain pin) exists as declared.
  • sense — read the live state, not the declaration.
  • judge — verified / drifted / could-not-verify.
  • act — apply idempotently where the plane granted authority (applyInstallation); otherwise report the gap with its blocker named (liveStateCouldNotVerify).
  • learn — a subject that is repeatedly declared-but-not-verifiable is a missing credential or a missing API, filed as a named absence rather than worked around inside this package.

The loop closes when a plane's own dashboard shows zero drifted subjects and zero could-not-verify subjects for a given run — not when it shows zero drifted alone, which is exactly the "nobody looked" result this package's whole design exists to keep from reading as "looks fine."

API

| Export | Kind | Description | | --- | --- | --- | | loadManifest(raw) | function | Validates a parsed provisioning manifest and returns it normalized | | createRuntimeContext(manifest, options) | function | Resolves the home directory, source root, workspace root, and token table | | planInstallation(manifest, runtime) | function | Resolves every manifest entry to absolute paths with no filesystem access | | expandTokens(value, tokens) | function | Expands ${TOKEN} placeholders; throws on an unknown token | | applyInstallation(plan, fs, options) | function | Applies a plan through the injected port, backing up every replaced destination | | verifyInstallation(plan, fs) | function | Reads the machine and returns Finding[] | | createNodeFileSystem() | function | The default FileSystemPort, backed by node:fs | | composeInstallationPlans(namedPlans) | function | Composes several named sources' plans into one, provenance-tagged; throws DestinationCollisionError on a shared destination | | applyComposedInstallation(namedPlans, fs, options) | function | Validates composition, then applies each source's plan through applyInstallation | | verifyComposedInstallation(namedPlans, fs) | function | Validates composition, then returns ComposedFinding[], each tagged with its source | | DestinationCollisionError | class | Thrown when two or more sources claim the same destination; carries collisions | | renderManagedBlock(body, start, end) | function | The marker-delimited block text | | withoutManagedBlock(contents, start, end) | function | Content with the managed region removed | | composeManagedBlock(existing, body, start, end, legacyBody?) | function | The exact content a destination should hold | | hasExactlyOneBlock(contents, body, start, end) | function | Whether a destination holds exactly one well-formed copy of a block | | PRIVATE_DIRECTORY_MODE | number | 0o700 | | LIVE_STATE_SURFACE_FINDING_KINDS | constant | All five finding kinds, frozen | | validateLiveStateSurfaceDeclaration(declaration) | function | Validates one LiveStateSurfaceDeclaration's own shape | | reconcileLiveState(input) | function | Reconciles one subject's declaration against one observation; returns a LiveStateSubjectReport | | liveStateVerified(subject) / liveStateDrifted(subject, findings) / liveStateCouldNotVerify(subject, blocker) | function | The only three constructors of a LiveStateSubjectReport | | liveStateReconciliationReasons | constant | The declared-but-not-verifiable reason vocabulary, scoped via createGateReasons | | validateRuntimePin(pin) / validatePackageManagerPin(pin) / validateBuildOrderPin(pin) | function | Validate one toolchain pin's own shape | | validateToolchainDeclaration(declaration) | function | Validates all three toolchain pins at once | | reconcileToolchain(declaration, observation) | function | Reconciles a declared toolchain against one machine observation; returns three LiveStateSubjectReports | | Finding / Severity | type | { rule, severity, message } and "high" \| "medium" \| "low" | | Manifest / LinkEntry / CopyEntry / ManagedBlockEntry / PrivateDirectoryEntry | type | The four manifest collections and their entries | | RuntimeContext / RuntimeOptions / Plan / PlanOperation / OperationKind | type | Runtime resolution and planning shapes | | FileSystemPort / FileStats | type | The injected filesystem interface | | ApplyOptions / ApplyResult | type | { backupRoot } and { changed, unchanged, backupRoot } | | NamedSourcePlan | type | { source, plan } — one source's already-computed Plan, tagged with its identifier | | ComposedPlan / ComposedPlanOperation | type | The composed plan and its provenance-tagged operations | | ComposedFinding | type | A Finding with the source whose plan produced it attached | | DestinationCollision | type | { destinationPath, sources } — the evidence behind a DestinationCollisionError | | LiveStateSurfaceDeclaration | type | The store / readableByScript / readableBy / reconciledBy / note declaration | | LiveStateSurfaceFindingKind / LiveStateDriftKind | type | All five kinds, and the four a completed attempt can report | | LiveStateFinding / LiveStateSubjectReport / LiveStateReconciliationResult / LiveStateReconciliationReason | type | One finding, one subject's report, and the underlying GateResult shapes | | LiveStateObservation / LiveStateDeclarationValue / ReconcileLiveStateInput | type |