@empressaio/atom-contract

中文文档
1.44.0发布于 2026/11/13TypeScriptES Module近 30 天下载 1.5万

The typed-data substrate every Empressa atom satisfies: identity, context interface, composition declaration, and history anchoring. Peer to the Hauska SDK (@hauska-sdk/*); consumed directly by every product surface and MCP server.

原始使用文档
文档目录20 节
  1. Install
  2. Publishing (operators)
  3. Public surface
  4. The four-layer contract
  5. Scope at the call site
  6. Composition
  7. Access policy (visibility tier)
  8. History ( EventAnchoringService )
  9. VDA wrapping (no-op today)
  10. Inline reference syntax
  11. Encumbrance atom types (ADR-020 / ADR-021)
  12. Oil & Gas atom types (ADR-025)
  13. Property reasoning atom kinds (master WDLL 3.2–3.6)
  14. Encumbrance render modes
  15. Display vocabulary (OPS-23 R-6 / P-167)
  16. Read-contract types (Calibrated Spine F4 / F6 / K6)
  17. Testing utilities
  18. What this package does NOT ship
  19. Lineage
  20. License

@empressaio/atom-contract

The typed-data substrate every Empressa atom must satisfy: identity, context interface, composition declaration, and history anchoring. Peer to the Hauska SDK (@hauska-sdk/*); consumed directly by every product surface and MCP server.

This package is framework only. It ships the contract, the runtime that enforces it, the schema the history layer writes into, and the test scaffolding consumer packages use to prove their atoms comply. Catalog atom registrations (the actual atom types — engagement, code-section, jurisdiction-corpus, etc.) live in consumer packages that depend on this one.

Empressa commercial substrate, peer to @hauska-sdk/* and hauska-engine, per doc_repo ADR-018 (atom contract substrate layer placement). The package has zero @hauska-sdk/* dependency: an MCP server or product surface can depend on the contract without inheriting the commerce stack.

Install

npm install @empressaio/atom-contract
# or
pnpm add @empressaio/atom-contract

Requires Node 18.18+ and drizzle-orm (^0.30.0) if you intend to use PostgresEventAnchoringService. Atoms that don't anchor history can use createInMemoryEventService() from the ./testing subpath.

Publishing (operators)

The hauska-sdk npm account uses staged publishing + Windows Hello (passkey 2FA), not granular tokens. See docs/npm-publish-automation.md.

.\publish-1.4.0.ps1
.\publish-approve-1.4.0.ps1 -StageId <uuid-from-stage-list>

Public surface

import {
  createAtomRegistry,
  defaultScope,
  resolveMode,
  parseInlineReferences,
  serializeInlineReference,
  httpContextSummary,
  resolveComposition,
  PostgresEventAnchoringService,
  wrapForStorage,
  unwrapFromStorage,
  type AccessPolicy,
  type AtomRegistration,
  type AtomMode,
  type AtomReference,
  type ContextSummary,
  type Scope,
  type AtomComposition,
  type EventAnchoringService,
} from "@empressaio/atom-contract";

Testing utilities live behind a separate subpath so production bundles never pull them in:

import {
  createTestRegistry,
  createInMemoryEventService,
  runAtomContractTests,
} from "@empressaio/atom-contract/testing";

The four-layer contract

An atom registers with one structurally-typed object:

const taskAtom: AtomRegistration<"task", ["card", "compact", "expanded"]> = {
  // identity
  entityType: "task",
  domain: "sprint",

  // render-mode contract (type-level)
  supportedModes: ["card", "compact", "expanded"],
  defaultMode: "card",

  // context interface
  contextSummary: async (entityId, scope) => ({
    prose: `Task ${entityId}: ...`,
    typed: { id: entityId },
    keyMetrics: [{ label: "Status", value: "open" }],
    relatedAtoms: [],
    historyProvenance: { latestEventId: "evt-...", latestEventAt: "2026-..." },
    scopeFiltered: false,
  }),

  // composition declaration (multi-child; required field)
  composition: [
    { childEntityType: "blocker", childMode: "compact", dataKey: "blockers" },
  ],
};

const registry = createAtomRegistry();
registry.register(taskAtom);

Compile-time guarantees:

  • defaultMode must be a member of supportedModes. Mismatched defaults fail to typecheck.
  • entityType is a literal string, narrowed all the way through registry.resolve("task") so consumers see the exact type back.
  • domain is required and queryable through registry.listByDomain(...).
  • composition is required. Pass [] to declare no children.

Five render modes ship as a literal union: inline, compact, card, expanded, focus. Render bindings (the React <AtomShell>, per-mode components, focus-store wiring) live in a separate package that depends on this one.

Scope at the call site

contextSummary always takes a second Scope argument:

interface Scope {
  audience: "ai" | "user" | "internal";
  requestor?: { kind: "user" | "agent"; id: string };
  asOf?: Date;
  permissions?: ReadonlyArray<string>;
}

Atoms that don't differentiate by scope ignore it and set scopeFiltered: false. Tests use the defaultScope() helper.

Composition

Composition is a multi-child declarative graph. The registry consumes the field: registry.validate() walks every registration's composition edges and reports any that point at an unregistered child entity type. Use resolveComposition(parent, parentRef, parentData, registry) to turn the declaration into a typed children list ready for render-side iteration.

Boot-time contract: the application bootstrap MUST call registry.validate() once after every register() call has run and fail to start when the result is { ok: false }. The registry does not revalidate composition on each register() (the parent may legitimately register before the child) and resolve() does not recheck on lookup, so dangling cross-references would otherwise surface only at composition-resolution time.

Composition edges may opt out of presence validation by setting forwardRef: true. Forward-ref edges are skipped by both validate() and resolveComposition while the child remains unregistered, so a parent atom can ship a declaration that names a child slated for a later sprint without crashing the boot.

Access policy (visibility tier)

Atoms may declare an ADR-017 access tier. The contract performs no enforcement; downstream surfaces (MCP list_*, catalog APIs) gate on the value.

type AccessPolicy =
  | "public-free"        // unauthenticated public catalog
  | "public-paid"        // catalog-visible, entitlement-gated at fetch
  | "platform-internal"  // platform staff only; never enumerated publicly
  | "tenant-private"     // owning tenant only
  | "tenant-shared";     // explicit cross-tenant share (ADR-017)

The field appears in two places:

  • AtomRegistration.accessPolicy? — atom-type default. Useful when the entire type is internal (e.g. an audit atom).
  • ContextSummary.accessPolicy? — per-instance value. Lets a mostly-public atom mark individual instances internal — for example, a public jurisdiction-corpus catalog where partnership-pending jurisdictions are tagged "platform-internal" until partnership closes.

Resolution: per-instance ContextSummary.accessPolicy wins when present, otherwise fall back to the registration's accessPolicy, otherwise treat the atom as "public-free". The prompt-builder catalog (registry.describeForPrompt()) normalizes the registration value to "public-free" when undeclared so downstream filters branch without nullish guards.

History (EventAnchoringService)

Every atom mutation flows through EventAnchoringService.appendEvent, which writes one row to an atom_events table. The shipped PostgresEventAnchoringService writes a deterministic SHA-256 chain hash. prevHash links each event to the previous event for the same (entityType, entityId) pair, producing a per-entity hash chain.

Interface-stable / implementation-evolving. The EventAnchoringService interface is the contract. The deterministic SHA-256 implementation will be replaced with a real cryptographic anchor (Merkle root + external ledger anchor) at a later milestone without changing the consumer interface. Mark sites that should be revisited carry TODO(M2-C): markers.

The host project owns the atom_events schema (table, columns, constraints). This library issues raw SQL against the agreed table name only and depends on drizzle-orm's sql helper for SQL templating.

VDA wrapping (no-op today)

wrapForStorage(value) returns { envelope: { version: 1, vdaApplied: false }, payload: value } and unwrapFromStorage(stored) returns stored.payload. Consumers call these from their write paths today; the no-op becomes a real envelope (version chain + tombstone semantics) at a later milestone without consumer changes.

Inline reference syntax

The chat layer embeds atoms in prose using {{atom|type|id|label}}:

parseInlineReferences("see {{atom|task|t1|Pick HVAC}}");
// -> [
//   { kind: "text", text: "see " },
//   { kind: "atom", reference: { kind: "atom", entityType: "task", entityId: "t1", displayLabel: "Pick HVAC" }, raw: "{{atom|task|t1|Pick HVAC}}" }
// ]

The delimiter is |. The previous shape used : and could not represent Spec 51 entityIds that themselves contain : (e.g. parcel-briefing:{parcelId}:{intentHash}). The old shape is no longer parsed: there is no dual-parse compatibility path.

Encumbrance atom types (ADR-020 / ADR-021)

Private recorded land-use instruments ship as Zod-validated payloads on the ./encumbrances subpath (v1.2.0+). Types are never public-free; schemas accept only tenant-private and tenant-shared.

import {
  RECORDED_INSTRUMENT_SCHEMA,
  RESTRICTION_CLAUSE_SCHEMA,
  ENCUMBRANCE_RENDER_MODES,
  SAMPLE_RECORDED_INSTRUMENT,
} from "@empressaio/atom-contract/encumbrances";

Oil & Gas atom types (ADR-025)

O&G operational, land, and capital atoms ship as Zod-validated payloads on the ./og subpath (v1.7.0). Covers the asset spine (well, wellbore, completion, zone, pad), production timeseries, equipment state, the land leg (mineral-lease, rrc-lease, tract, ownership-interest), and revenue allocation units. Obligation (core domain-neutral type) ships in the main module, not ./og.

import {
  WELL_SCHEMA,
  MINERAL_LEASE_SCHEMA,
  REVENUE_ALLOCATION_UNIT_SCHEMA,
  SAMPLE_WELL,
  OG_DEFAULT_ACCESS_POLICY,
} from "@empressaio/atom-contract/og";

Property reasoning atom kinds (master WDLL 3.2–3.6)

Parcel buildable-answer chain atoms ship on the ./property subpath (v1.9.0+): parcel-node, zoning-fact, setback-rule, buildable-envelope, parcel-terrain-model, road-node, building-footprint, utility-easement, plat-building-line (1.44.0). All are data-tier (atomTier: "data") and default to public-free accessPolicy when undeclared on the registration.

Calibrated confidence at READ (I-E): instances MAY carry a ReasoningReadContract asserted snapshot at write time, including a placeholder calibratedConfidence with provenance: "asserted". At READ, the calibrated axis resolves through the calibration overlay — it is NOT composed-and-frozen on the instance. Do not add a labeling x district multiply field.

import {
  ZONING_FACT_SCHEMA,
  SETBACK_RULE_SCHEMA,
  BUILDABLE_ENVELOPE_SCHEMA,
  HAYS_ZONING_FACT_FIXTURE,
  BEXAR_NULL_ZONING_FACT_FIXTURE,
  HAYS_BUILDABLE_ENVELOPE_FIXTURE,
  type ZoningFactAtomInstance,
  type SetbackRuleAtomInstance,
  type BuildableEnvelopeAtomInstance,
} from "@empressaio/atom-contract/property";
entityTypereasoningKindKey fields
parcel-nodeobservedkeyKind, geometryStoreRef pointer (never the ring) OR absence/verifiedAbsence
zoning-factobserveddistrict OR absence.no-zoning-stamp
setback-ruleobservedfront/side/rear, typed sourceCodeAtomRef, matchBasis
buildable-envelopederivedbuildable-envelope-inset-v1, input refs to fact + rule + geometry
parcel-terrain-modelderivedparcel-terrain-mesh-ifc-v1, format-keyed artifacts
road-nodeobserved{fips}:road:{osm_way_id}, centerline + assumed ROW
building-footprintobservedfootprintGeometry OR absence/verifiedAbsence; sourceTier
utility-easementobservedeasementGeometry, easementClass, public-free always; optional easementKind, placement, platRef (1.44.0)
plat-building-lineobservedlineGeometry/offsetDistanceFt, appliesTo, platRef, placement OR absence (1.44.0)

parcel-node is the Rail 1 anchor every other property rail keys on. It records parcel identity and ring PROVENANCE; the ring itself stays in txgio_parcel, the one geometry truth frame (Geometry Law rule 1). geometryStoreRef is a strict pointer, so an inlined ring fails to parse.

ICC royalties on code citations accrue via core ObligationAtomInstance rows — no standalone SourceAttribution type.

Encumbrance render modes

entityTypePurposeRecommended render modes
recorded-instrumentParent instrument; wet PDF via sourceDocumentCidcard, compact, expanded
restriction-clauseEnforceable snippet; plan-review citation targetinline … focus (default focus)
restriction-corpusSubdivision CC&R packcard, expanded
administrative-ruleUnrecorded HOA guidelines (legalWeight: advisory)inline, compact, card, expanded
constraint-resolutionEffective constraint lattice (ADR-021)card, expanded

Engine AtomRegistration literals and ingest producers belong in hauska-engine/packages/atoms/ (cc-agent-E); Cortex Phase 1 may validate uploads with these schemas before the engine registry lands.

Display vocabulary (OPS-23 R-6 / P-167)

The human-facing display strings for a claim ship on the ./display subpath (v1.32.0+), so the Property Explorer panel, the feasibility PDF, and the Smart Site MCP connector render the same words for the same atom from one place instead of hand-synced copies. Two modules:

  • buildable: mapBuildableDisplay and its types — the buildable/setback card, PDF, and cross-surface agreement-token vocabulary. Moved verbatim from the five byte-identical buildable-display-vocab.ts copies that used to live in hauska-map and hauska-engine, locked together by parity fixtures.
  • wire: VOCABULARY, WIRE_DISPOSITION_DISPLAY_TEXT, DERIVED_FIGURES_POLICY — the Smart Site MCP wire-token vocabulary table. Moved verbatim from legacy-design-tools artifacts/smartsite-mcp/src/vocabulary.ts (the P-91 v3 table), along with the nine display constants it used to import from the MCP server's own mcp-app.ts, so this module has no dependency on the MCP server.
import {
  mapBuildableDisplay,
  type BuildableDisplayInput,
  VOCABULARY,
  WIRE_DISPOSITION_DISPLAY_TEXT,
} from "@empressaio/atom-contract/display";

As of 1.32.0 no consumer imports this subpath yet and the prior copies still exist; retiring them (deleting the copies, the parity locks, and the skipping vendor-drift test) is a later step of the same plan row, gated on P-153 merging in hauska-map and legacy-design-tools.

Read-contract types (Calibrated Spine F4 / F6 / K6)

Widthed, three-axis confidence returned at read time. Unwidthed, unsourced confidence is unrepresentable: the estimate is a branded nominal type constructible only through createWidthedConfidence. No derived numbers are stored in this package.

import {
  createReadContract,
  createWidthedConfidence,
  READ_CONTRACT_SCHEMA,
  SAMPLE_READ_CONTRACT,
  type ReadContract,
  type CalibrationProvenance,
} from "@empressaio/atom-contract/read-contract";
ExportRole
WidthedConfidenceInseparable estimate + n + intervalWidth + provenance
ThreeAxisConfidenceAccuracy, source-quality, and consequence axes
ReadContractFull read-contract object every surface must emit
ModelAttributionStampLedger deposit stamp (model, prompt, atom-set id)
CalibrationProvenanceasserted | backtest | seed | live

Propagation to MCP, cortex-api, Cortex, extension, and map is a consumer-side Wave 2 co-bump after pinning ^1.4.0.

Testing utilities

Consumer packages import the contract test suite to prove their registration is well-formed in one function call:

import { describe } from "vitest";
import { runAtomContractTests } from "@empressaio/atom-contract/testing";
import { taskAtom } from "./task.reg";

describe("task atom contract", () => {
  runAtomContractTests(taskAtom, {
    withFixture: { entityId: "t1", setUp: seedTestData },
    alsoRegister: [/* any composition children */],
  });
});

The suite asserts:

  1. identity is present and entityType is non-empty,
  2. defaultMode is in supportedModes,
  3. contextSummary returns a valid four-layer shape with historyProvenance and scopeFiltered,
  4. every composition edge resolves against alsoRegister + this,
  5. inline-reference round-trips for an instance of this atom.

createInMemoryEventService() provides an in-memory EventAnchoringService for unit tests that don't need Postgres; createTestRegistry(initial) is a convenience over createAtomRegistry().

What this package does NOT ship

  • Catalog atom registrations. No engagement.atom.ts, code-section.atom.ts, etc. Catalog atoms live in consumer packages (hauska-engine/packages/atoms/, product api-servers, etc.) that depend on this one. The line: this package owns the registration mechanism and the contract; consumers own the per-atom instances.
  • React rendering layer. Type-level render-mode contract only. The <AtomRenderer>, <AtomShell>, per-mode components, focus-store wiring, and right-panel state machine all ship in a later sibling package.
  • Cryptographic anchoring of atom_events. chainHash is deterministic SHA-256 today; Merkle root + external anchor land in a later milestone.
  • Real VDA backing. wrapForStorage / unwrapFromStorage are no-ops; the real envelope, version chain, and tombstone semantics land in a later milestone.

Lineage

This package extracts the workspace-private @workspace/empressa-atom that lived at legacy-design-tools/lib/empressa-atom/. The framework is preserved verbatim; only the package name and the @workspace/db test-time dependency changed.

License

See LICENSE.

版本与发布记录

@empressaio/atom-contract 共发布 39 个版本,首次发布于 2026/07/07;过去 12 个月发布了 39 个版本。

版本发布日期距今
1.44.0latest2026/11/13今天
1.43.02026/11/112 天前
1.42.02026/11/067 天前
1.41.02026/11/067 天前
1.40.02026/11/067 天前
1.39.02026/11/067 天前

关于 @empressaio/atom-contract 的常见问题

@empressaio/atom-contract 是什么?

The typed-data substrate every Empressa atom satisfies: identity, context interface, composition declaration, and history anchoring. Peer to the Hauska SDK (@hauska-sdk/*); consumed directly by every product surface and MCP server.

如何安装 @empressaio/atom-contract?

在项目目录运行 npm install @empressaio/atom-contract,或使用 pnpm add / yarn add / bun add。

@empressaio/atom-contract 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@empressaio/atom-contract 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@empressaio/atom-contract 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18.18.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@empressaio/atom-contract 可以商用吗?

@empressaio/atom-contract 使用 未声明 许可。package.json 没有声明许可证。没有许可证的代码默认保留全部权利,商用前请联系作者或查看仓库中的 LICENSE 文件。以上为概括,不构成法律意见,请以许可原文为准。

@empressaio/atom-contract 的下载量有多少?

根据 npm 官方下载统计,@empressaio/atom-contract 在 2026/10/14 – 2026/11/12 这 30 天内被下载 14,973 次,日均约 499 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@empressaio/atom-contract 还在维护吗?

最新版本 1.44.0 发布于 2026/11/13(今天);至今共发布 39 个版本,其中过去 12 个月发布了 39 个;首次发布于 2026/07/07。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。