@adlc/tickets

中文文档
1.12.0发布于 2026/10/10MITTypeScriptES Module零依赖构建来源已验证近 30 天下载 9187

Immutable, Git-native ticket stores and policy services for ADLC.

原始使用文档
文档目录4 节
  1. Discovering the ticket shape
  2. Writing to a frozen trust root
  3. Known residuals
  4. Durability

@adlc/tickets

The canonical ADLC ticket domain. It provides immutable logical snapshots, strict legacy/directory/Git-revision stores, backend-independent hashes, intent-specific mutations, recoverable filesystem transactions, migration, archive, and diagnostics.

The package has no runtime dependencies. All writers are dry-run-first through adlc ticket; it never stages or commits Git changes. Existing .adlc/tickets.json repositories remain supported during the 1.x bridge and migrate only with explicit approval. New stores use one deterministic JSON shard per ticket under .adlc/tickets/.

Discovering the ticket shape

The CLI is self-describing — nothing needs to be inferred by reading an existing ticket out of the store:

adlc ticket <command> --help   # flags plus the full input document for that command
adlc ticket create --help      # every field, what reads it, and a runnable example
adlc ticket schema             # the ticket JSON Schema on stdout

create --help prints a worked example you can pipe straight back in:

adlc ticket create --input - < ticket.json

Both --help and schema work before a store exists. Omit id on create: the service mints a ULID (T-01K…) — there is no "next free T<n>" to derive.

lib/help.mjs holds the one field table the help text and the published schemas/ticket.schema.json are both generated from, and test/help.test.mjs fails if the committed schema drifts from it or if a field the validator polices goes undocumented.

Writing to a frozen trust root

Once any ticket declares rails, the store stops being ordinary data: it is the configuration deciding what the rail guards freeze, so freezing it too is the only thing that stops one edit from disabling enforcement. The PreToolUse rail hook has always denied a structured edit to .adlc/tickets/** from that moment on. The store service now holds the same line for its own writers, so the audit no longer depends on which door a write came through:

  • every --write against such a store appends one signed ticket-mutation entry to the gate-manifest, recording the operation, ticket id(s), and the store hash either side of the change (bypass: true);
  • a mutation that already records evidence of its own — rail narrowing, completion, reassignment, archive, migration — keeps its existing ticket-<operation> entry and gains the audit fields. One mutation is never two entries;
  • with no ADLC_MANIFEST_KEY the write refuses before touching the store. An unsigned entry proves nothing about who made the change, and the manifest is append-only, so writing one anyway would be permanent noise. --allow-unsigned (mirroring adlc prosecute record-cross-model) records one deliberately and warns;
  • the rule holds on every door into the store, not just adlc ticket: the legacy → sharded migration and its recovery, archive/restore, an interrupted transaction's recovery, ticket-prune's direct tickets.json write, and the published 1.x LegacyTicketStore.write(tickets), which still works but now routes through the audited transaction. A contract enforced on one writer and not the others is not a contract;
  • ticket-prune is the one writer that cannot make the store write and the manifest append a single act, so it stages, records, then renames. If the rename fails the entry is already appended and names a store hash that was never reached — an append-only ledger cannot retract that, so prune appends a correction (action: 'abandoned', before and after hash equal) saying the store did not move. If the correction cannot be written either, the failure says so explicitly rather than leaving the caller to assume the ledger is true. The correction runs when the rename throws; a crash or power loss in the same window leaves the false claim uncorrected, detectable by comparing the entry's storeHashAfter to the store but not repaired automatically — this path has no journal and no recovery pass;
  • a repo where no ticket declares a rail — in the active set or the archive — is not a trust root: authoring there needs no key and records nothing. Completing, archiving or discarding the last railed ticket does not thaw it, because that removal is itself an audited override and the manifest keeps the record after the ticket is gone (#162). The one case this cannot see is a repo that removed its last rail before this change and never had a hook override recorded — nothing on disk distinguishes it from a repo that never used rails; see lib/trust-root.mjs. That set cannot grow, since every removal from this change forward leaves a marker, and it shrinks as each such repo takes one audited write. Closing it needs a durable adoption record, tracked by ticket T-01M0TMRSQKGTNWZTFXBPQ2JHNB.

Known residuals

Two states this contract does not cover. Both are recorded here so the guarantee is not read as wider than it is; each is tracked for closure by its own ticket.

  • A crash during ticket-prune's audit-then-rename window. The compensating entry runs when the rename throws; a crash or power loss between the manifest append and the rename leaves an entry naming a store hash the store never reached. It stays detectable — that hash will not match the store — but this path has no journal and no recovery pass, so nothing repairs it automatically. The ordering is deliberate: recording after a completed write risks a real mutation with no record at all, which is undetectable rather than merely wrong. Tracked by T-01M0WNX6P09HWKDK429XQ8GGRJ.
  • A parent-directory swap during export. The destination is validated, then the mkdir, temp write and rename address it by path, so a local writer able to rename a parent of the destination can redirect the write after the check. Node offers no openat/O_NOFOLLOW for that sequence, so the path cannot be pinned to a directory handle. It requires write access to a parent of the operator's chosen destination plus winning the race; write access to the repo makes the race unnecessary, and a sticky bit blocks it in the usual world-writable case. Tracked by T-01M0WNX6ZA0D94HW2VQKZAPGQ2.

Durability

Sensitive mutations use recoverable, evidence-bearing transactions on both supported 1.x backends. POSIX persistence flushes changed files and parent-directory entries; Windows flushes files and relies on the documented platform recovery tests because Node does not provide portable directory fsync.

版本与发布记录

@adlc/tickets 共发布 11 个版本,首次发布于 2026/07/15;过去 12 个月发布了 11 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
1.12.0latest2026/10/1027 天前
1.11.12026/09/091 个月前
1.11.02026/08/252 个月前
1.10.02026/08/112 个月前
1.9.02026/08/092 个月前
1.8.02026/08/082 个月前

关于 @adlc/tickets 的常见问题

@adlc/tickets 是什么?

Immutable, Git-native ticket stores and policy services for ADLC.

如何安装 @adlc/tickets?

在项目目录运行 npm install @adlc/tickets,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 adlc-tickets,可以用 npx adlc-tickets 直接运行。

@adlc/tickets 支持 TypeScript 吗?

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

@adlc/tickets 支持 ESM 还是 CommonJS?

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

@adlc/tickets 需要什么版本的 Node.js?

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

@adlc/tickets 可以商用吗?

@adlc/tickets 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@adlc/tickets 的下载量有多少?

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

@adlc/tickets 还在维护吗?

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