@lapxo/bound 0.1.1 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,8 +5,8 @@
5
5
  Declare what must hold. Read what does. Keep the evidence
6
6
 
7
7
  ```bash
8
- npx @lapxo/bound@0.1.1 fold --as help
9
- npx @lapxo/bound@0.1.1 fold
8
+ npx @lapxo/bound@0.1.3 fold --as help
9
+ npx @lapxo/bound@0.1.3 fold
10
10
  ```
11
11
 
12
12
  ## How Bound reads a project
@@ -49,11 +49,11 @@ lot_dir="$(mktemp -d)"
49
49
  Have the selected view produce a proposal at `$lot_dir/proposal.bound`, or prepare the project's permitted lines there for review. Unsigned proposals use `by=target`, without a preassigned epoch or `sig`. Lots stay outside the repository; the committed bound files are only `TARGET.bound` and `receipts.bound`.
50
50
 
51
51
  ```sh
52
- npx @lapxo/bound@0.1.1 sign "$lot_dir/proposal.bound" \
52
+ npx @lapxo/bound@0.1.3 sign "$lot_dir/proposal.bound" \
53
53
  --key "$BOUND_KEY_ID" --key-file "$BOUND_KEY_FILE" \
54
54
  > "$lot_dir/signed.bound" &&
55
- npx @lapxo/bound@0.1.1 land "$lot_dir/signed.bound" &&
56
- npx @lapxo/bound@0.1.1 fold
55
+ npx @lapxo/bound@0.1.3 land "$lot_dir/signed.bound" &&
56
+ npx @lapxo/bound@0.1.3 fold
57
57
  ```
58
58
 
59
59
  `sign` assigns the admitted epoch and writes the signed lot to stdout. `land` validates the whole lot before admission. A refusal is evidence to inspect; it is not success. Do not proceed after a failed signature or admission. The key file is a PKCS8 private key matching the admitted public key; do not put it in the lot, source tree, package or CI artifacts.
package/TARGET.bound CHANGED
@@ -1,4 +1,4 @@
1
- bound-lock/1 about="# @lapxo/bound\n\nDeclare what must hold. Read what does. Keep the evidence.\n\nBound reads a project's admitted statements, checks observations against its requirements, and produces the views its selected topos declares. It keeps conflicting evidence and its origins instead of choosing a convenient answer.\n\n`fold` reads. `sign` signs a proposed lot. `land` admits that lot under the place's authority. These acts work across domains: a selected topos supplies forms, units, readers and views.\n\n## Start in an admitted project\n\n```sh\nnpx @lapxo/bound@0.1.1 fold --as help\nnpx @lapxo/bound@0.1.1 fold\n```\n\nThe help view prints:\n\n```text\nverbs fold land sign\nflags --as --check --key --key-file --signer --place\n```\n\nThe default fold runs the project's declared views. Its authority and selected topos determine what can be admitted, read and shown.\n\nStart with the team's accepted TARGET, admitted authority and selected readers. [Full usage guide](https://github.com/Lapxo/bound/blob/2053bb6d6188f04538114eae6a0add5166944ed3/docs/usage.md).\n\n## A meeting everyone can understand\n\nAna is available from 10 to 14, Luis from 12 to 16, and the room from 10 to 13. Their common availability is 12 to 13. A boss available from 18 to 20 makes the encounter incompatible. Withdrawing that exact reading restores the overlap; Ana, Luis and the room remain.\n\nWith an interval form, those are object operations: observe independent claims, meet them, withdraw by id, and read the state. A narrower compatible meet is not a fork. Folding a file of declarations alone does not calculate meeting availability.\n\nThe same pattern applies to a delivery window, a sensor's allowed range, or an agreed performance budget. The chosen topos determines the units and how evidence is obtained. Bound does not supply a meeting-specific rule.\n\n## See how information holds together\n\n![Cells, travelling bounds and a local join](https://raw.githubusercontent.com/Lapxo/bound/2053bb6d6188f04538114eae6a0add5166944ed3/docs/readme-assets/atlas.svg)\n\nA travelling ceiling on B reaches C and G through their declared rest. A local join widens B alone. D is independent. The figure shows those relations; point size and camera distance do not measure information.\n\n| State | Meaning |\n| --- | --- |\n| REQUIRED | Compatible bounds, fewer than two independent origins. |\n| FREE | Independent origins meet and their encounter fits the bounds. |\n| CONFLICT | Bounds conflict, or eligible claims have no common encounter. |\n| FORBIDDEN | An inhabited encounter does not fit the bounds. |\n\nBounds are checked before origins and claims. A receipt is evidence of a reading, not another origin. Duplicating one does not add freedom.\n\n## Change a project without erasing evidence\n\nWork in the project's directory. Use an existing admitted key identifier and its matching private key stored outside every repository. The admitted key also names its signer with `keys/<who> measure=signer value=file`; `signer/timeout` and `signer/response-bytes` declare its limits. Your public key and coverage belong to the authority contract; possession of a private key alone does not authorize a scope.\n\n```sh\nexport BOUND_KEY_ID='your-admitted-key-id'\nexport BOUND_KEY_FILE=\"$HOME/Private/keys/your-device.pem\"\nlot_dir=\"$(mktemp -d)\"\n```\n\nHave the selected view produce a proposal at `$lot_dir/proposal.bound`, or prepare the project's permitted lines there for review. Unsigned proposals use `by=target`, without a preassigned epoch or `sig`. Lots stay outside the repository; the committed bound files are only `TARGET.bound` and `receipts.bound`.\n\n```sh\nnpx @lapxo/bound@0.1.1 sign \"$lot_dir/proposal.bound\" \\\n --key \"$BOUND_KEY_ID\" --key-file \"$BOUND_KEY_FILE\" \\\n > \"$lot_dir/signed.bound\" &&\nnpx @lapxo/bound@0.1.1 land \"$lot_dir/signed.bound\" &&\nnpx @lapxo/bound@0.1.1 fold\n```\n\n`sign` assigns the admitted epoch and writes the signed lot to stdout. `land` validates the whole lot before admission. A refusal is evidence to inspect; it is not success. Do not proceed after a failed signature or admission. The key file is a PKCS8 private key matching the admitted public key; do not put it in the lot, source tree, package or CI artifacts.\n\nFor typed object withdrawals, `takes` names the exact claim or mark id. Removing a local join leaves the travelling sign live. For untyped configuration, use the wire's exact withdrawal selectors; do not combine withdrawal and replacement with identical selectors in one lot. Wire alphabet updates supersede by epoch.\n\n[Authority, withdrawals, receipts and PR review](docs/usage.md).\n\n## Read a declared view and reuse its evidence\n\nWhen the project has admitted a cells view:\n\n```sh\nnpx @lapxo/bound@0.1.1 fold --as cells\n```\n\nThe installed interval scene printed this on an unchanged second reading:\n\n```text\nRECEIPTS 4 read · 0 opened · idle · provider 0\n```\n\nNative receipts identify the live semantic inputs, declared rest, selected topos, codecs and selected rendering implementation. They exclude delivery count, display paths and Bound's error wrapper. A local join changes its own cell's receipt. A travelling sign changes dependent cells. Missing or corrupt carried bytes cannot prove reuse.\n\nFor judgement:\n\n```sh\nnpx @lapxo/bound@0.1.1 fold --check\n```\n\nKeep stdout, stderr and the exit status. `--check` is not an install-and-build replacement. A source test passing does not pay an unread ceiling, prove a cold clone, or accept a release.\n\n## Select a topos by what it declares\n\n`sources/<name>` says where a world can be obtained. An admitted `uses/<name>` names the digest of that topos's folded standing: canonical live claims in wire byte order, excluding signature envelopes and host shape. Re-signing the same standing preserves the pin. A changed live form, class, view or offer changes it.\n\n`restsOn` closes artifact bytes separately. A capsule is one offer. Forms, classes and views need no capsule merely to be selected. Runtime and packing do not define standing identity. npm dependency ranges remain package metadata; they are not standing pins.\n\n| Responsibility | Owner |\n| --- | --- |\n| Meet, state, live acts and rest | Obligations |\n| Wire, forms, classes, views and offer contracts | Topos |\n| Admission, folding and evidence | Bound |\n| Domain measurement and representation | Selected topos |\n| Storage, transport and execution | Host |\n\nCell `restsOn` is the object's relation. Directory prefixes and file summaries are host coordinates, not object rest. Wire tokens are protocol constants, not organization, host, path or key literals.\n\n## Repository rules\n\nCommit `TARGET.bound`, `receipts.bound` and the outputs the forge reads. Ignore `.bound/` from the first line of `.gitignore`. Keep proposals, signed lots, keys, ledgers, caches and dependency blobs outside the published tree.\n\nThe lock is the project's own contract and pins. Read a dependency's declarations from its selected standing; do not copy its lines into your lock. README, package metadata and workflows must be renders of admitted declarations. A scan is an observation, not permission to lower a ceiling.\n\nA PR proposes lines and the leaf changes they render. The judge uses the team's accepted contract independently of the proposed revision. Preserve refusals, conflicting origins and unpaid demands. [Repository and contribution rules](https://github.com/Lapxo/bound/blob/2053bb6d6188f04538114eae6a0add5166944ed3/docs/usage.md#repository-and-pr-review).\n\n## An atlas you can question\n\nInformation can share a history without sharing a single presentation. The same admitted cells can support a compact status view, a spatial view of their relations, or a focused reading of one disagreement. Each view asks for a region and resolution; its selected topos determines the representation.\n\nThis makes changes inspectable. A local act can remain local, travelling bounds can reach dependent cells, and a receipt can name the inputs of the reading you saw. You can ask what changed and what depends on it without turning a drawing into the algebra.\n\nA pin names standing, not a download URL or runtime entry. Its declared artifact dependencies close the bytes needed for an offered operation. The boundary lets a form or view remain meaningful even when it has no executable capsule.\n\nUse Bound where independently attributed observations must be judged against an accepted contract: compatibility between components, operational limits, or a team's project requirements. The selected topos must supply the actual measurements; a digest identifies the evidence without declaring it true.\n\n[Contribute a counterexample or a proposed lot](CONTRIBUTING.md).\n" at=policy:bound/011-signing-docs by=target form=alphabet measure=text role=writes scope=prose/en/files/public-0 shape=README.md value=lock
1
+ bound-lock/1 about="# @lapxo/bound\n\nDeclare what must hold. Read what does. Keep the evidence.\n\nBound reads a project's admitted statements, checks observations against its requirements, and produces the views its selected topos declares. It keeps conflicting evidence and its origins instead of choosing a convenient answer.\n\n`fold` reads. `sign` signs a proposed lot. `land` admits that lot under the place's authority. These acts work across domains: a selected topos supplies forms, units, readers and views.\n\n## Start in an admitted project\n\n```sh\nnpx @lapxo/bound@0.1.3 fold --as help\nnpx @lapxo/bound@0.1.3 fold\n```\n\nThe help view prints:\n\n```text\nverbs fold land sign\nflags --as --check --key --key-file --signer --place\n```\n\nThe default fold runs the project's declared views. Its authority and selected topos determine what can be admitted, read and shown.\n\nStart with the team's accepted TARGET, admitted authority and selected readers. [Full usage guide](https://github.com/Lapxo/bound/blob/2053bb6d6188f04538114eae6a0add5166944ed3/docs/usage.md).\n\n## A meeting everyone can understand\n\nAna is available from 10 to 14, Luis from 12 to 16, and the room from 10 to 13. Their common availability is 12 to 13. A boss available from 18 to 20 makes the encounter incompatible. Withdrawing that exact reading restores the overlap; Ana, Luis and the room remain.\n\nWith an interval form, those are object operations: observe independent claims, meet them, withdraw by id, and read the state. A narrower compatible meet is not a fork. Folding a file of declarations alone does not calculate meeting availability.\n\nThe same pattern applies to a delivery window, a sensor's allowed range, or an agreed performance budget. The chosen topos determines the units and how evidence is obtained. Bound does not supply a meeting-specific rule.\n\n## See how information holds together\n\n![Cells, travelling bounds and a local join](https://raw.githubusercontent.com/Lapxo/bound/2053bb6d6188f04538114eae6a0add5166944ed3/docs/readme-assets/atlas.svg)\n\nA travelling ceiling on B reaches C and G through their declared rest. A local join widens B alone. D is independent. The figure shows those relations; point size and camera distance do not measure information.\n\n| State | Meaning |\n| --- | --- |\n| REQUIRED | Compatible bounds, fewer than two independent origins. |\n| FREE | Independent origins meet and their encounter fits the bounds. |\n| CONFLICT | Bounds conflict, or eligible claims have no common encounter. |\n| FORBIDDEN | An inhabited encounter does not fit the bounds. |\n\nBounds are checked before origins and claims. A receipt is evidence of a reading, not another origin. Duplicating one does not add freedom.\n\n## Change a project without erasing evidence\n\nWork in the project's directory. Use an existing admitted key identifier and its matching private key stored outside every repository. The admitted key also names its signer with `keys/<who> measure=signer value=file`; `signer/timeout` and `signer/response-bytes` declare its limits. Your public key and coverage belong to the authority contract; possession of a private key alone does not authorize a scope.\n\n```sh\nexport BOUND_KEY_ID='your-admitted-key-id'\nexport BOUND_KEY_FILE=\"$HOME/Private/keys/your-device.pem\"\nlot_dir=\"$(mktemp -d)\"\n```\n\nHave the selected view produce a proposal at `$lot_dir/proposal.bound`, or prepare the project's permitted lines there for review. Unsigned proposals use `by=target`, without a preassigned epoch or `sig`. Lots stay outside the repository; the committed bound files are only `TARGET.bound` and `receipts.bound`.\n\n```sh\nnpx @lapxo/bound@0.1.3 sign \"$lot_dir/proposal.bound\" \\\n --key \"$BOUND_KEY_ID\" --key-file \"$BOUND_KEY_FILE\" \\\n > \"$lot_dir/signed.bound\" &&\nnpx @lapxo/bound@0.1.3 land \"$lot_dir/signed.bound\" &&\nnpx @lapxo/bound@0.1.3 fold\n```\n\n`sign` assigns the admitted epoch and writes the signed lot to stdout. `land` validates the whole lot before admission. A refusal is evidence to inspect; it is not success. Do not proceed after a failed signature or admission. The key file is a PKCS8 private key matching the admitted public key; do not put it in the lot, source tree, package or CI artifacts.\n\nFor typed object withdrawals, `takes` names the exact claim or mark id. Removing a local join leaves the travelling sign live. For untyped configuration, use the wire's exact withdrawal selectors; do not combine withdrawal and replacement with identical selectors in one lot. Wire alphabet updates supersede by epoch.\n\n[Authority, withdrawals, receipts and PR review](docs/usage.md).\n\n## Read a declared view and reuse its evidence\n\nWhen the project has admitted a cells view:\n\n```sh\nnpx @lapxo/bound@0.1.3 fold --as cells\n```\n\nThe installed interval scene printed this on an unchanged second reading:\n\n```text\nRECEIPTS 4 read · 0 opened · idle · provider 0\n```\n\nNative receipts identify the live semantic inputs, declared rest, selected topos, codecs and selected rendering implementation. They exclude delivery count, display paths and Bound's error wrapper. A local join changes its own cell's receipt. A travelling sign changes dependent cells. Missing or corrupt carried bytes cannot prove reuse.\n\nFor judgement:\n\n```sh\nnpx @lapxo/bound@0.1.3 fold --check\n```\n\nKeep stdout, stderr and the exit status. `--check` is not an install-and-build replacement. A source test passing does not pay an unread ceiling, prove a cold clone, or accept a release.\n\n## Select a topos by what it declares\n\n`sources/<name>` says where a world can be obtained. An admitted `uses/<name>` names the digest of that topos's folded standing: canonical live claims in wire byte order, excluding signature envelopes and host shape. Re-signing the same standing preserves the pin. A changed live form, class, view or offer changes it.\n\n`restsOn` closes artifact bytes separately. A capsule is one offer. Forms, classes and views need no capsule merely to be selected. Runtime and packing do not define standing identity. npm dependency ranges remain package metadata; they are not standing pins.\n\n| Responsibility | Owner |\n| --- | --- |\n| Meet, state, live acts and rest | Obligations |\n| Wire, forms, classes, views and offer contracts | Topos |\n| Admission, folding and evidence | Bound |\n| Domain measurement and representation | Selected topos |\n| Storage, transport and execution | Host |\n\nCell `restsOn` is the object's relation. Directory prefixes and file summaries are host coordinates, not object rest. Wire tokens are protocol constants, not organization, host, path or key literals.\n\n## Repository rules\n\nCommit `TARGET.bound`, `receipts.bound` and the outputs the forge reads. Ignore `.bound/` from the first line of `.gitignore`. Keep proposals, signed lots, keys, ledgers, caches and dependency blobs outside the published tree.\n\nThe lock is the project's own contract and pins. Read a dependency's declarations from its selected standing; do not copy its lines into your lock. README, package metadata and workflows must be renders of admitted declarations. A scan is an observation, not permission to lower a ceiling.\n\nA PR proposes lines and the leaf changes they render. The judge uses the team's accepted contract independently of the proposed revision. Preserve refusals, conflicting origins and unpaid demands. [Repository and contribution rules](https://github.com/Lapxo/bound/blob/2053bb6d6188f04538114eae6a0add5166944ed3/docs/usage.md#repository-and-pr-review).\n\n## An atlas you can question\n\nInformation can share a history without sharing a single presentation. The same admitted cells can support a compact status view, a spatial view of their relations, or a focused reading of one disagreement. Each view asks for a region and resolution; its selected topos determines the representation.\n\nThis makes changes inspectable. A local act can remain local, travelling bounds can reach dependent cells, and a receipt can name the inputs of the reading you saw. You can ask what changed and what depends on it without turning a drawing into the algebra.\n\nA pin names standing, not a download URL or runtime entry. Its declared artifact dependencies close the bytes needed for an offered operation. The boundary lets a form or view remain meaningful even when it has no executable capsule.\n\nUse Bound where independently attributed observations must be judged against an accepted contract: compatibility between components, operational limits, or a team's project requirements. The selected topos must supply the actual measurements; a digest identifies the evidence without declaring it true.\n\n[Contribute a counterexample or a proposed lot](CONTRIBUTING.md).\n" at=witness:bound/013-contract-closure by=target form=alphabet measure=text role=writes scope=prose/en/files/public-0 shape=README.md value=lock
2
2
  bound-lock/1 about="# Contributing to Bound\n\nBring a counterexample, an observation or a proposed lot. Include the inputs and the output that led you to it. A signature establishes who was authorized to speak; it does not make their result true.\n\n## Set up a working copy\n\nClone the repository and use the Node version supported by package.json. If package-lock.json is present, run npm ci; otherwise use npm install --ignore-scripts --no-package-lock. Then run:\n\n```sh\nnpm run build\nnpm test\n```\n\nThese are development checks, not project acceptance. For a place with admitted trust context and native receipts, run the product judgement as well:\n\n```sh\nnpx @lapxo/bound@0.1.0 fold --check\n```\n\nPreserve a refusal and its exit status in your report. Do not borrow a neighbouring checkout's ledger or substitute local bytes for a named pin.\n\n## Make the smallest useful change\n\nBound owns admission, folding and evidence. Topos owns grammar, forms, classes, views and their contracts. A selected topos owns domain measurement. Obligations owns the algebra. Keep each change in its layer; do not put a domain decoder, path-based inference or a fallback for a missing pin into Bound.\n\nAn algebra change needs an independent oracle and a counterexample. A host change needs evidence for authentic bytes, failure propagation or reuse. A view change needs its declared region and implementation identity. A test should fail for a plausible incorrect implementation, not assert a source string or repeat the same expression on both sides.\n\nKeep regressions for changed signatures, unauthorized coverage, exact-id withdrawal, conflicting evidence, local versus travelling acts, corrupt content and duplicate delivery. Consolidate repeated setup and duplicate scenarios without removing distinct falsifiers.\n\n## Propose and admit\n\nKeep proposal and signed-lot files outside the repository. Use the current admitted wire, by=target, and no preassigned epoch or signature. The team's admitted key signs the lot; Bound admits it under scope coverage. The [usage guide](docs/usage.md) explains the transaction and external key-file requirements.\n\nSend the proposed lines with the leaf changes they render. A hand-edited render is evidence of the intended change, not an admitted contribution. Do not edit the accepted TARGET to make a test or ceiling pass. If a render disagrees with the lock, fix the proposed lines and re-render it.\n\nCommit only the project's TARGET.bound and native receipts.bound as bound files. The first .gitignore line excludes .bound/. Do not commit keys, stores, ledgers, caches, capsules, archives or vendored dependencies.\n\n## Submit a pull request\n\nExplain the concrete behavior before and after the change, the affected layer, and the checks you ran. Include stdout, stderr and exit status for a CLI case. State which reader and input bytes produced the evidence; a source harness is not an installed-package proof.\n\nPolicy changes receive explicit team review separately from measurements of the proposed source. Keep incompatible readings and their origins visible. Never remove a failing test or weaken a ceiling to obtain acceptance. Withdraw statements through their admitted history; do not rewrite prior signatures, tags or releases.\n\nDocumentation and workflows follow the same review: propose their lines and include the rendered changes.\n" at=policy:public-prose by=target form=alphabet measure=text role=writes scope=prose/en/files/public-1 shape=CONTRIBUTING.md value=lock
3
3
  bound-lock/1 about="# Using Bound\n\nThis guide covers the current CLI in a place whose wire and authority are already admitted. A fresh unsigned TARGET is a proposal, not a trusted project. The prepared repository is not yet a certified empty-clone bootstrap.\n\n## Before the first fold\n\nYou need the team's accepted TARGET and trust context, authentic bytes for each selected digest, and the readers/views that TARGET names. Do not borrow a neighbouring repository's ledger or copy node_modules to make a missing pin look available.\n\nThe published repository carries exactly TARGET.bound and receipts.bound as bound files. Local admission history lives in the ignored .bound/ store. Published standing excludes the signature envelope; owner public keys retain their signatures.\n\nStart by asking for help and folding the project. If a selected view is unavailable, use the named refusal and its fix; do not invent an output with a similar name.\n\n## Keys and coverage\n\nUse a named key already admitted by the project. The authority contract declares its public key, class and scope coverage. Signing uses a declared port. The lock names `keys/<who> measure=signer value=<name>`; `signer/timeout` and `signer/response-bytes` bound each invocation. The built-in `file` port reads a matching PKCS8 PEM outside the repository through `--key-file`. For a command port, the host supplies a `BOUND_SIGNERS` registry mapping that admitted name to `{ \"kind\": \"command\", \"executable\": \"...\", \"args\": [] }`. A repository declaration cannot authorize a program. `--signer <name>` must match the admitted selection.\n\nA new device must have its public key and coverage admitted by an authority that already covers the intended scope. The CLI does not currently certify silent key creation or tap-to-sign. This guide does not substitute a self-authorized fixture key for the team's authority.\n\nFor automation through the file port, supply the private key through the runner's secret storage to a temporary external file with restricted permissions. A command signer receives the lot once as canonical signed bytes, one line per record in order. It returns one canonical wire response per record, containing `by` and `sig`, in that same order. Bound verifies the entire reply against the admitted public key before any admission. Timeout, nonzero exit, missing or invalid replies refuse the whole lot; raw signer output is not logged. Hosts can keep private keys outside Bound through this port. Key generation, storage and rotation belong to the host. Do not print, commit or upload it. A source-control token and a Bound authority key have different purposes. Key rotation and revocation are changes to the admitted authority contract, not edits that erase prior signatures.\n\n## Proposal → signature → admission → reading\n\nPrepare the proposed lines outside the repository. Each line must use the current wire's fields, forms and required set. Configuration retains its legacy required fields, including `at`. Typed cells, claims and marks use their exact row grammar; `role=reads` or `at` cannot repair an incomplete typed record.\n\nA proposal names `by=target` and omits epoch/signature. The CLI assigns epoch from admitted history when signing. Never copy an epoch from an example or change a signed line's body.\n\nUse the signing and admission transaction in the README. Shell `&&` prevents a failed sign from being followed by land. Successful signing alone does not mean admission. Successful admission alone does not mean every requirement is paid. Fold after land and inspect its states, receipts and exit status.\n\nCLI sign authenticates wire inscriptions. It is different from the object's travelling ceiling sign. The selected Topos decodes spans and supplies origin/witness context; Bound must not guess those from by, widens or a path.\n\n## Reading and withdrawing\n\nAsk only for a view admitted by the project, such as cells. A local join widens that cell with a witness; it leaves the travelling ceiling live. A travelling require supplies a floor. Claims carry distinct origins; their signatures do not by themselves establish independent origins.\n\nFor typed histories, a negative record names the exact id in takes. Withdrawing a claim removes that live observation; withdrawing a join removes that local widening, not the sign. A fresh claim with the same content is a new identity, not resurrection of a withdrawn id. Re-delivery of the same signed bytes is idempotent.\n\nFor configuration, the negative line preserves the exact non-envelope selectors and changes value to withdraw. Do not withdraw and replace identical selectors in the same batch. Use a new epoch when superseding an alphabet declaration. Never withdraw evidence merely to obtain a green verdict.\n\n## Receipts and refusals\n\nreceipts.bound is native fold output. Do not write it by hand, use JSON from a test as a receipt, or copy receipts from another tree. Unchanged inputs can reuse verified region receipts; missing bytes require recomputation or a refusal. A duplicate receipt does not create another origin.\n\nREFUSE·wire means the current grammar does not admit the record. REFUSE·pin/release means named authentic content or its closure is unavailable or does not match. REFUSE·view means the requested view cannot be rendered under its contract. Authority refusal means the signer is not covered. Keep the line and the failed exit status; follow an explicit fix without changing the named digest or dropping a conflicting statement.\n\nrelease/blocks is policy read by the fold. Its admitted category links select existing obligations. The fold prints each missing reading or crossed ceiling as BLOCK; a blocked or incomplete policy is not READY. Read the captured verdict for the exact candidate instead of inferring acceptance from a successful render.\n\n## Repository and PR review\n\nThe accepted TARGET is independent of a contributor's scan. Commit native receipts and admitted renders, not local .bound/ state. A page disagreeing with TARGET must be re-rendered; a ceiling is not lowered to fit the page.\n\nA contribution is a proposed lot with the leaf changes rendered from it. Send that lot for admission by the team's coverage. Do not submit hand-edited lock output as already accepted. Check secrets, extra bound files, foreign vendoring and uncaptured page claims before review.\n\nA review includes the product fold, meaningful suites and verification of the rendered bytes. Preserve their actual output and exit status.\n" at=policy:bound/011-signing-docs by=target form=alphabet measure=text role=writes scope=prose/en/files/public-2 shape=docs/usage.md value=lock
4
4
  bound-lock/1 about=", & " at=policy:bound/010 by=target form=alphabet measure=text role=writes scope=prose/en/cite/and value=lock
@@ -10,16 +10,14 @@ bound-lock/1 about="A fold" at=policy:bound/readme by=target form=alphabet measu
10
10
  bound-lock/1 about="A fold, live" at=policy:bound/door by=target form=alphabet measure=text role=writes scope=prose/en/seq/heading value=lock
11
11
  bound-lock/1 about="A line" at=policy:bound/readme by=target form=alphabet measure=text role=writes scope=prose/en/figure/line/heading value=present
12
12
  bound-lock/1 about="A lock is the set of lines a place is bound by. A receipt is what a reader saw. The fold is their meet. Each line carries scope, role, form, measure, value, needs, at, by, epoch, sig." at=policy:bound/010 by=target form=alphabet measure=text role=writes scope=prose/en/the-object value=present
13
- bound-lock/1 about="A place selects its own worlds by standing digest; selection travels through the existing place projection." at=policy:bound/places by=target form=alphabet measure=id role=reads scope=place/speaks value=uses/*
14
13
  bound-lock/1 about="A second independent reader must agree. The fold rebuilds from the files alone, offline." at=witness:project-coordinates-are-relative by=target form=alphabet kind=axiom measure=status needs=samples/yes/verify-agrees.json|samples/no/verify-agrees.json role=demands scope=law/verify-agrees value=present
15
14
  bound-lock/1 about="Bind a package release with the pipeline views. Bind a page to its receipts with view/readme. Refuse a dependency drift with dep pins. Ask a second head that must agree." at=policy:bound/010 by=target form=alphabet measure=text role=writes scope=prose/en/learn value=present
16
15
  bound-lock/1 about="Bound is the application of that algebra: signed bounds, folded by one meet, refused when they fork." at=policy:bound/010 by=target form=alphabet measure=text role=writes scope=prose/en/paper value=present
17
- bound-lock/1 about="Bound reads a project's admitted statements and observations through its selected Topos, then renders the declared views and records their inputs in receipts. The example below explains the object; a file-fold alone does not calculate its intervals.\n\n## A meeting everyone can understand\n\nAna is available from 10 to 14, Luis from 12 to 16, and the room from 10 to 13. Their common availability is 12 to 13. A boss available from 18 to 20 makes the encounter incompatible. Withdrawing that exact reading restores the overlap; Ana, Luis and the room remain.\n\nWith an interval form, those are object operations: observe independent claims, meet them, withdraw by id, and read the state. A narrower compatible meet is not a fork. Folding a file of declarations alone does not calculate meeting availability.\n\nThe same pattern applies to a delivery window, a sensor's allowed range, or an agreed performance budget. The chosen topos determines the units and how evidence is obtained. Bound does not supply a meeting-specific rule.\n\n## See how information holds together\n\n![Cells, travelling bounds and a local join](https://raw.githubusercontent.com/Lapxo/bound/2053bb6d6188f04538114eae6a0add5166944ed3/docs/readme-assets/atlas.svg)\n\nA travelling ceiling on B reaches C and G through their declared rest. A local join widens B alone. D is independent. The figure shows those relations; point size and camera distance do not measure information.\n\n| State | Meaning |\n| --- | --- |\n| REQUIRED | Compatible bounds, fewer than two independent origins. |\n| FREE | Independent origins meet and their encounter fits the bounds. |\n| CONFLICT | Bounds conflict, or eligible claims have no common encounter. |\n| FORBIDDEN | An inhabited encounter does not fit the bounds. |\n\nBounds are checked before origins and claims. A receipt is evidence of a reading, not another origin. Duplicating one does not add freedom.\n\n## Change a project without erasing evidence\n\nWork in the project's directory. Use an existing admitted key identifier and its matching private key stored outside every repository. The admitted key also names its signer with `keys/<who> measure=signer value=file`; `signer/timeout` and `signer/response-bytes` declare its limits. Your public key and coverage belong to the authority contract; possession of a private key alone does not authorize a scope.\n\n```sh\nexport BOUND_KEY_ID='your-admitted-key-id'\nexport BOUND_KEY_FILE=\"$HOME/Private/keys/your-device.pem\"\nlot_dir=\"$(mktemp -d)\"\n```\n\nHave the selected view produce a proposal at `$lot_dir/proposal.bound`, or prepare the project's permitted lines there for review. Unsigned proposals use `by=target`, without a preassigned epoch or `sig`. Lots stay outside the repository; the committed bound files are only `TARGET.bound` and `receipts.bound`.\n\n```sh\nnpx @lapxo/bound@0.1.1 sign \"$lot_dir/proposal.bound\" \\\n --key \"$BOUND_KEY_ID\" --key-file \"$BOUND_KEY_FILE\" \\\n > \"$lot_dir/signed.bound\" &&\nnpx @lapxo/bound@0.1.1 land \"$lot_dir/signed.bound\" &&\nnpx @lapxo/bound@0.1.1 fold\n```\n\n`sign` assigns the admitted epoch and writes the signed lot to stdout. `land` validates the whole lot before admission. A refusal is evidence to inspect; it is not success. Do not proceed after a failed signature or admission. The key file is a PKCS8 private key matching the admitted public key; do not put it in the lot, source tree, package or CI artifacts.\n\nFor typed object withdrawals, `takes` names the exact claim or mark id. Removing a local join leaves the travelling sign live. For untyped configuration, use the wire's exact withdrawal selectors; do not combine withdrawal and replacement with identical selectors in one lot. Wire alphabet updates supersede by epoch.\n\n[Authority, withdrawals, receipts and PR review](https://github.com/Lapxo/bound/blob/2053bb6d6188f04538114eae6a0add5166944ed3/docs/usage.md).\n\n## Select a topos by what it declares\n\n`sources/<name>` says where a world can be obtained. An admitted `uses/<name>` names the digest of that topos's folded standing: canonical live claims in wire byte order, excluding signature envelopes and host shape. Re-signing the same standing preserves the pin. A changed live form, class, view or offer changes it.\n\n`restsOn` closes artifact bytes separately. A capsule is one offer. Forms, classes and views need no capsule merely to be selected. Runtime and packing do not define standing identity. npm dependency ranges remain package metadata; they are not standing pins.\n\n| Responsibility | Owner |\n| --- | --- |\n| Meet, state, live acts and rest | Obligations |\n| Wire, forms, classes, views and offer contracts | Topos |\n| Admission, folding and evidence | Bound |\n| Domain measurement and representation | Selected topos |\n| Storage, transport and execution | Host |\n\nCell `restsOn` is the object's relation. Directory prefixes and file summaries are host coordinates, not object rest. Wire tokens are protocol constants, not organization, host, path or key literals.\n\n## Repository rules\n\nCommit `TARGET.bound`, `receipts.bound` and the outputs the forge reads. Ignore `.bound/` from the first line of `.gitignore`. Keep proposals, signed lots, keys, ledgers, caches and dependency blobs outside the published tree.\n\nThe lock is the project's own contract and pins. Read a dependency's declarations from its selected standing; do not copy its lines into your lock. README, package metadata and workflows must be renders of admitted declarations. A scan is an observation, not permission to lower a ceiling.\n\nA PR proposes lines and the leaf changes they render. The judge uses the team's accepted contract independently of the proposed revision. Preserve refusals, conflicting origins and unpaid demands. [Repository and contribution rules](https://github.com/Lapxo/bound/blob/2053bb6d6188f04538114eae6a0add5166944ed3/docs/usage.md#repository-and-pr-review).\n\n## An atlas you can question\n\nInformation can share a history without sharing a single presentation. The same admitted cells can support a compact status view, a spatial view of their relations, or a focused reading of one disagreement. Each view asks for a region and resolution; its selected topos determines the representation.\n\nThis makes changes inspectable. A local act can remain local, travelling bounds can reach dependent cells, and a receipt can name the inputs of the reading you saw. You can ask what changed and what depends on it without turning a drawing into the algebra.\n\nA pin names standing, not a download URL or runtime entry. Its declared artifact dependencies close the bytes needed for an offered operation. The boundary lets a form or view remain meaningful even when it has no executable capsule.\n\nUse Bound where independently attributed observations must be judged against an accepted contract: compatibility between components, operational limits, or a team's project requirements. The selected topos must supply the actual measurements; a digest identifies the evidence without declaring it true.\n\n[Contribute a counterexample or a proposed lot](CONTRIBUTING.md)." at=policy:bound/011-signing-docs by=target form=alphabet measure=text role=writes scope=prose/en/idea value=present
16
+ bound-lock/1 about="Bound reads a project's admitted statements and observations through its selected Topos, then renders the declared views and records their inputs in receipts. The example below explains the object; a file-fold alone does not calculate its intervals.\n\n## A meeting everyone can understand\n\nAna is available from 10 to 14, Luis from 12 to 16, and the room from 10 to 13. Their common availability is 12 to 13. A boss available from 18 to 20 makes the encounter incompatible. Withdrawing that exact reading restores the overlap; Ana, Luis and the room remain.\n\nWith an interval form, those are object operations: observe independent claims, meet them, withdraw by id, and read the state. A narrower compatible meet is not a fork. Folding a file of declarations alone does not calculate meeting availability.\n\nThe same pattern applies to a delivery window, a sensor's allowed range, or an agreed performance budget. The chosen topos determines the units and how evidence is obtained. Bound does not supply a meeting-specific rule.\n\n## See how information holds together\n\n![Cells, travelling bounds and a local join](https://raw.githubusercontent.com/Lapxo/bound/2053bb6d6188f04538114eae6a0add5166944ed3/docs/readme-assets/atlas.svg)\n\nA travelling ceiling on B reaches C and G through their declared rest. A local join widens B alone. D is independent. The figure shows those relations; point size and camera distance do not measure information.\n\n| State | Meaning |\n| --- | --- |\n| REQUIRED | Compatible bounds, fewer than two independent origins. |\n| FREE | Independent origins meet and their encounter fits the bounds. |\n| CONFLICT | Bounds conflict, or eligible claims have no common encounter. |\n| FORBIDDEN | An inhabited encounter does not fit the bounds. |\n\nBounds are checked before origins and claims. A receipt is evidence of a reading, not another origin. Duplicating one does not add freedom.\n\n## Change a project without erasing evidence\n\nWork in the project's directory. Use an existing admitted key identifier and its matching private key stored outside every repository. The admitted key also names its signer with `keys/<who> measure=signer value=file`; `signer/timeout` and `signer/response-bytes` declare its limits. Your public key and coverage belong to the authority contract; possession of a private key alone does not authorize a scope.\n\n```sh\nexport BOUND_KEY_ID='your-admitted-key-id'\nexport BOUND_KEY_FILE=\"$HOME/Private/keys/your-device.pem\"\nlot_dir=\"$(mktemp -d)\"\n```\n\nHave the selected view produce a proposal at `$lot_dir/proposal.bound`, or prepare the project's permitted lines there for review. Unsigned proposals use `by=target`, without a preassigned epoch or `sig`. Lots stay outside the repository; the committed bound files are only `TARGET.bound` and `receipts.bound`.\n\n```sh\nnpx @lapxo/bound@0.1.3 sign \"$lot_dir/proposal.bound\" \\\n --key \"$BOUND_KEY_ID\" --key-file \"$BOUND_KEY_FILE\" \\\n > \"$lot_dir/signed.bound\" &&\nnpx @lapxo/bound@0.1.3 land \"$lot_dir/signed.bound\" &&\nnpx @lapxo/bound@0.1.3 fold\n```\n\n`sign` assigns the admitted epoch and writes the signed lot to stdout. `land` validates the whole lot before admission. A refusal is evidence to inspect; it is not success. Do not proceed after a failed signature or admission. The key file is a PKCS8 private key matching the admitted public key; do not put it in the lot, source tree, package or CI artifacts.\n\nFor typed object withdrawals, `takes` names the exact claim or mark id. Removing a local join leaves the travelling sign live. For untyped configuration, use the wire's exact withdrawal selectors; do not combine withdrawal and replacement with identical selectors in one lot. Wire alphabet updates supersede by epoch.\n\n[Authority, withdrawals, receipts and PR review](https://github.com/Lapxo/bound/blob/2053bb6d6188f04538114eae6a0add5166944ed3/docs/usage.md).\n\n## Select a topos by what it declares\n\n`sources/<name>` says where a world can be obtained. An admitted `uses/<name>` names the digest of that topos's folded standing: canonical live claims in wire byte order, excluding signature envelopes and host shape. Re-signing the same standing preserves the pin. A changed live form, class, view or offer changes it.\n\n`restsOn` closes artifact bytes separately. A capsule is one offer. Forms, classes and views need no capsule merely to be selected. Runtime and packing do not define standing identity. npm dependency ranges remain package metadata; they are not standing pins.\n\n| Responsibility | Owner |\n| --- | --- |\n| Meet, state, live acts and rest | Obligations |\n| Wire, forms, classes, views and offer contracts | Topos |\n| Admission, folding and evidence | Bound |\n| Domain measurement and representation | Selected topos |\n| Storage, transport and execution | Host |\n\nCell `restsOn` is the object's relation. Directory prefixes and file summaries are host coordinates, not object rest. Wire tokens are protocol constants, not organization, host, path or key literals.\n\n## Repository rules\n\nCommit `TARGET.bound`, `receipts.bound` and the outputs the forge reads. Ignore `.bound/` from the first line of `.gitignore`. Keep proposals, signed lots, keys, ledgers, caches and dependency blobs outside the published tree.\n\nThe lock is the project's own contract and pins. Read a dependency's declarations from its selected standing; do not copy its lines into your lock. README, package metadata and workflows must be renders of admitted declarations. A scan is an observation, not permission to lower a ceiling.\n\nA PR proposes lines and the leaf changes they render. The judge uses the team's accepted contract independently of the proposed revision. Preserve refusals, conflicting origins and unpaid demands. [Repository and contribution rules](https://github.com/Lapxo/bound/blob/2053bb6d6188f04538114eae6a0add5166944ed3/docs/usage.md#repository-and-pr-review).\n\n## An atlas you can question\n\nInformation can share a history without sharing a single presentation. The same admitted cells can support a compact status view, a spatial view of their relations, or a focused reading of one disagreement. Each view asks for a region and resolution; its selected topos determines the representation.\n\nThis makes changes inspectable. A local act can remain local, travelling bounds can reach dependent cells, and a receipt can name the inputs of the reading you saw. You can ask what changed and what depends on it without turning a drawing into the algebra.\n\nA pin names standing, not a download URL or runtime entry. Its declared artifact dependencies close the bytes needed for an offered operation. The boundary lets a form or view remain meaningful even when it has no executable capsule.\n\nUse Bound where independently attributed observations must be judged against an accepted contract: compatibility between components, operational limits, or a team's project requirements. The selected topos must supply the actual measurements; a digest identifies the evidence without declaring it true.\n\n[Contribute a counterexample or a proposed lot](CONTRIBUTING.md)." at=witness:bound/013-contract-closure by=target form=alphabet measure=text role=writes scope=prose/en/idea value=present
18
17
  bound-lock/1 about="Cite it as {name}." at=policy:bound/010 by=target form=alphabet measure=text role=writes scope=prose/en/cff/name value=lock
19
18
  bound-lock/1 about="Cite it by the paper it rests on, Two-Sided Constraints on a Valued Lattice." at=policy:bound/010 by=target form=alphabet measure=text role=writes scope=prose/en/cff/paper value=lock
20
19
  bound-lock/1 about="Declare what must hold. Read what does. Keep the evidence." at=policy:bound/historical-prose by=target form=alphabet measure=text role=writes scope=docs/tagline value=present
21
20
  bound-lock/1 about="Declared runtime processes participate in the instrument implementation identity." at=policy:bound/receipts by=target form=alphabet kind=process measure=id needs=dist/cli/host.js|dist/cli/observe.js|dist/host/located.js|dist/host/provide.js role=writes scope=process/runtime value=present
22
- bound-lock/1 about="Expose the place own release policy and its selected obligations through the existing place projection." at=policy:bound/places by=target form=alphabet measure=id role=reads scope=place/speaks value=uses/*|view/*/**|reader/*/**|notation/*/**|idempotent|one-writer|nothing-deleted|law/one-lot-one-place|cli/**|fold/idle|cost/act|literal/**|duplicate/src|door/unreceipted|resolve-by-url|offline|the-stranger|release/**
23
21
  bound-lock/1 about="First session" at=policy:bound/readme by=target form=alphabet measure=id role=writes scope=page/seq/heading value=present
24
22
  bound-lock/1 about="First use" at=policy:bound/readme by=target form=alphabet measure=text role=writes scope=prose/en/commands/heading value=lock
25
23
  bound-lock/1 about="How Bound reads a project" at=policy:bound/historical-prose by=target form=alphabet measure=text role=writes scope=prose/en/idea/heading value=lock
@@ -39,8 +37,6 @@ bound-lock/1 about="Pending general document projection at docs/readme-assets/at
39
37
  bound-lock/1 about="Pending general document projection at docs/usage.md; no automatic output is claimed by this capability demand." at=policy:own-prose by=target form=alphabet measure=id role=demands scope=view/files/public-2 value=text@3
40
38
  bound-lock/1 about="Pending general document projection at package.json; no automatic output is claimed by this capability demand." at=policy:own-prose by=target form=alphabet measure=id role=demands scope=view/files/manifest value=text@3
41
39
  bound-lock/1 about="Preview with fold --as before it lands." at=policy:bound/readme by=target form=alphabet measure=id role=writes scope=page/seq/look value=present
42
- bound-lock/1 about="Read on" at=policy:bound/010 by=target form=alphabet measure=text role=writes scope=prose/en/pointers/heading value=lock
43
- bound-lock/1 about="Read on" at=policy:bound/prose by=target form=alphabet measure=text role=writes scope=prose/en/pointers/heading value=lock
44
40
  bound-lock/1 about="Release selection of existing obligations; their fold evidence determines the verdict." at=policy:bound/release by=target form=alphabet measure=id role=writes scope=release/category/admission value=idempotent|one-writer|nothing-deleted
45
41
  bound-lock/1 about="Release selection of existing obligations; their fold evidence determines the verdict." at=policy:bound/release by=target form=alphabet measure=id role=writes scope=release/category/cli value=cli/**|fold/idle|cost/act
46
42
  bound-lock/1 about="Release selection of existing obligations; their fold evidence determines the verdict." at=policy:bound/release by=target form=alphabet measure=id role=writes scope=release/category/integrity value=literal/host|literal/key|literal/org|literal/path|duplicate/src|door/unreceipted
@@ -52,6 +48,7 @@ bound-lock/1 about="The explicitly selected historical document profile over thi
52
48
  bound-lock/1 about="The native public receipt carries its actual observed set at resolution 8 before the authenticated summary at resolution 0 and current region bytes at resolution 1." at=witness:a-public-receipt-carries-its-observations by=target form=alphabet measure=id role=demands scope=view/receipts shape=receipts.bound value=receipts@8|receipts@0|receipts@1
53
49
  bound-lock/1 about="The object" at=policy:bound/010 by=target form=alphabet measure=text role=writes scope=prose/en/the-object/heading value=lock
54
50
  bound-lock/1 about="The paper" at=policy:bound/010 by=target form=alphabet measure=text role=writes scope=prose/en/the-paper/heading value=lock
51
+ bound-lock/1 about="The same signed lot and admitted snapshot share admission validation; a prospective reading neither admits nor writes. The vector reports failures; unread is not agreement." at=witness:prospective-admission-agrees by=target form=interval measure=count needs=samples/admission/yes/one-boundary.json|samples/admission/no/one-boundary.json|test/admission-boundary.test.ts|vectors/process/admission-boundary.vector.json role=reads scope=admission/one-boundary value=0..0
55
52
  bound-lock/1 about="This place own build option, observed from its source tsconfig for the selected historical build profile." at=policy:bound/build-profile by=target form=alphabet measure=id role=writes scope=build/allowImportingTsExtensions shape=tsconfig.json value=true
56
53
  bound-lock/1 about="This place own build option, observed from its source tsconfig for the selected historical build profile." at=policy:bound/build-profile by=target form=alphabet measure=id role=writes scope=build/composite shape=tsconfig.json value=true
57
54
  bound-lock/1 about="This place own build option, observed from its source tsconfig for the selected historical build profile." at=policy:bound/build-profile by=target form=alphabet measure=id role=writes scope=build/declaration shape=tsconfig.json value=true
@@ -118,7 +115,7 @@ bound-lock/1 about="lines, not a page: blobs, laid snapshots and answer caches n
118
115
  bound-lock/1 about="name: release\non:\n workflow_run:\n workflows: [tag]\n types: [completed]\n push:\n tags: ['v*']\njobs:\n release:\n if: github.event_name == 'push' || (github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.head_branch == 'main')\n runs-on: ubuntu-latest\n permissions:\n contents: write\n outputs:\n tag: ${{ steps.version.outputs.tag }}\n asset: ${{ steps.archive.outputs.asset }}\n sha256: ${{ steps.archive.outputs.sha256 }}\n steps:\n - uses: actions/checkout@v4\n with:\n ref: ${{ github.event.workflow_run.head_sha || github.sha }}\n fetch-depth: 0\n - id: version\n env:\n EVENT_NAME: ${{ github.event_name }}\n REF_NAME: ${{ github.ref_name }}\n run: |\n version=\"v$(node -p \"require('./package.json').version\")\"\n if [ \"$EVENT_NAME\" = push ]; then\n test \"$REF_NAME\" = \"$version\"\n fi\n git rev-parse --verify \"refs/tags/$version\"\n echo \"tag=$version\" >> \"$GITHUB_OUTPUT\"\n git checkout --detach \"$version\"\n - uses: actions/setup-node@v4\n with:\n node-version: 24\n - run: npm ci\n - run: npm run build\n - run: npm test\n - run: node dist/cli/verb.js fold --check\n - id: archive\n env:\n GH_TOKEN: ${{ github.token }}\n run: |\n version=\"${{ steps.version.outputs.tag }}\"\n asset=\"$(npm pack --ignore-scripts --silent)\"\n echo \"asset=$asset\" >> \"$GITHUB_OUTPUT\"\n expected=\"$(sha256sum \"$asset\" | cut -d ' ' -f 1)\"\n echo \"sha256=$expected\" >> \"$GITHUB_OUTPUT\"\n if ! gh release view \"$version\" >/dev/null 2>&1; then\n gh release create \"$version\" \"$asset\" --title \"$version\" --notes \"$(git log -1 --format=%B)\"\n fi\n download_dir=\"$(mktemp -d)\"\n gh release download \"$version\" --pattern \"$asset\" --dir \"$download_dir\"\n actual=\"$(sha256sum \"$download_dir/$asset\" | cut -d ' ' -f 1)\"\n test \"$expected\" = \"$actual\"\n publish:\n needs: release\n environment: release\n runs-on: ubuntu-latest\n permissions:\n contents: read\n id-token: write\n steps:\n - uses: actions/checkout@v4\n with:\n ref: ${{ needs.release.outputs.tag }}\n - uses: actions/setup-node@v4\n with:\n node-version: 24\n - run: npm install --global npm@'12.1.0'\n - env:\n GH_TOKEN: ${{ github.token }}\n TAG: ${{ needs.release.outputs.tag }}\n ASSET: ${{ needs.release.outputs.asset }}\n EXPECTED: ${{ needs.release.outputs.sha256 }}\n run: |\n mkdir candidate\n gh release download \"$TAG\" --pattern \"$ASSET\" --dir candidate\n actual=\"$(sha256sum \"candidate/$ASSET\" | cut -d ' ' -f 1)\"\n test \"$EXPECTED\" = \"$actual\"\n npm stage publish \"./candidate/$ASSET\" --access public --ignore-scripts\n" at=policy:bound/011-port-release by=target form=alphabet measure=text role=writes scope=prose/en/files/workflow/release shape=.github/workflows/release.yml value=lock
119
116
  bound-lock/1 about="name: tag\non:\n workflow_run:\n workflows: [test]\n types: [completed]\njobs:\n tag:\n if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'push' && github.event.workflow_run.head_branch == 'main'\n runs-on: ubuntu-latest\n permissions:\n contents: write\n steps:\n - uses: actions/checkout@v4\n with:\n ref: ${{ github.event.workflow_run.head_sha }}\n fetch-depth: 0\n - run: |\n version=\"v$(node -p \"require('./package.json').version\")\"\n git tag --list \"$version\" | grep -q . && exit 0\n git tag \"$version\" && git push origin \"$version\"\n" at=policy:bound/pipeline by=target form=alphabet measure=text role=writes scope=prose/en/files/workflow/tag shape=.github/workflows/tag.yml value=lock
120
117
  bound-lock/1 about="name: test\non:\n push:\n branches: [main]\n pull_request:\njobs:\n test:\n strategy:\n fail-fast: false\n matrix:\n node: [22, 24]\n runs-on: ubuntu-latest\n permissions:\n contents: read\n steps:\n - uses: actions/checkout@v4\n - uses: actions/setup-node@v4\n with:\n node-version: ${{ matrix.node }}\n - run: npm ci\n - run: npm run build\n - run: npm test\n - name: Verify native closure and receipt reuse\n run: |\n node dist/cli/verb.js fold --check > \"$RUNNER_TEMP/bound-check.log\" 2>&1 || { cat \"$RUNNER_TEMP/bound-check.log\"; exit 1; }\n cat \"$RUNNER_TEMP/bound-check.log\"\n grep -Eq '^PASS +closed' \"$RUNNER_TEMP/bound-check.log\"\n grep -Eq '^RECEIPTS [1-9][0-9]* read · 0 opened' \"$RUNNER_TEMP/bound-check.log\"\n - run: npm pack --silent\n" at=policy:bound/pipeline by=target form=alphabet measure=text role=writes scope=prose/en/files/workflow/test shape=.github/workflows/test.yml value=lock
121
- bound-lock/1 about="npx @lapxo/bound@0.1.1 fold --as help\nnpx @lapxo/bound@0.1.1 fold" at=policy:bound/011-port-release by=target form=alphabet measure=text role=writes scope=prose/en/commands value=lock
118
+ bound-lock/1 about="npx @lapxo/bound@0.1.3 fold --as help\nnpx @lapxo/bound@0.1.3 fold" at=witness:bound/013-contract-closure by=target form=alphabet measure=text role=writes scope=prose/en/commands value=lock
122
119
  bound-lock/1 about="obligations is the meet. topos is the line. Worlds outside bound read a calendar, a package, a page. Bound reads lines, folds them, lands what was signed, and signs." at=policy:bound/010 by=target form=alphabet measure=text role=writes scope=prose/en/rests/on value=present
123
120
  bound-lock/1 about="one origin is REQUIRED, two that meet inside every bound are FREE, two that meet outside a bound are FORBIDDEN, and two that do not meet are CONFLICT" at=policy:bound/010-states by=target form=alphabet measure=id role=writes scope=notation/states value="one origin=REQUIRED|claims that meet and fit the bounds=FREE|claims that meet outside the bounds=FORBIDDEN|bounds that cross=CONFLICT"
124
121
  bound-lock/1 about="take the package in" at=policy:bound/door by=target form=alphabet measure=id role=writes scope=install value=npx
@@ -126,7 +123,7 @@ bound-lock/1 about="the DOI prefix a cite is read through" at=policy:bound/010 b
126
123
  bound-lock/1 about="the TypeScript capsule nine places still pin, before source/longest became a maximum: named here until no uses line names it" at=policy:bound/capsules by=target form=alphabet measure=id role=writes scope=dep/topos-typescript-tree/pinned shape=dist/index.js value=sha256:8c29831ead1c231f8f444d49e64adb293bd4e0b854192b225a539c288a927ed6
127
124
  bound-lock/1 about="the TypeScript capsule readers: source/longest emitted under the measure longest, a maximum, and sibling refs as a count" at=policy:bound/capsules by=target form=alphabet measure=id role=writes scope=dep/topos-typescript-tree/readers shape=dist/index.js value=sha256:bf8a5a67c8a675ed04349e72c36a8f073a5c153646e98d2003b36a3fad34209e
128
125
  bound-lock/1 about="the algebra and the notation this place rests on" at=policy:bound/010 by=target form=alphabet measure=id role=writes scope=dependencies value=@lapxo/obligations|@lapxo/topos
129
- bound-lock/1 about="the algebra bound folds with, named by the digest of its release and read from bound-s own store, never from a sibling of the tree" at=witness:a-notation-that-asked-an-algebra-newer-than-the-one-laid by=target form=alphabet measure=id needs=node_modules/@lapxo/obligations role=reads scope=dep/obligations value=sha256:0a5bc5c61df28a3b7b2798869ab9aac74b98904856806a30d1755d26808f2254
126
+ bound-lock/1 about="the algebra bound folds with, named by the digest of its release and read from bound-s own store, never from a sibling of the tree" at=witness:public-artifact-closure by=target form=alphabet measure=id needs=node_modules/@lapxo/obligations role=reads scope=dep/obligations shape=package value=sha256:e4b48f480e86c2e1b9518b7a5dbe18e4b4dfa449961c781a291295fa85a3ec72
130
127
  bound-lock/1 about="the atoms of the door, in that order, each fed by a page/readme line or a receipt" at=policy:bound/readme by=target form=alphabet measure=id role=writes scope=page/readme value=group|say|term|count|cell|seq|exchange|set|ref
131
128
  bound-lock/1 about="the badge host a page wears" at=policy:bound/010 by=target form=alphabet measure=id role=writes scope=sources/badges value=https://img.shields.io/badge
132
129
  bound-lock/1 about="the boss is withdrawn; the meet is again the range every remaining origin allows" at=policy:bound/door by=target form=alphabet measure=text role=writes scope=prose/en/figure/fold/back value=lock
@@ -152,7 +149,6 @@ bound-lock/1 about="the coordinates of bound the entries region is handed: the e
152
149
  bound-lock/1 about="the coordinates of bound the hazards region is handed: the whole place" at=policy:bound/regions by=target form=alphabet measure=coordinates role=reads scope=region/hazards value=**
153
150
  bound-lock/1 about="the coordinates of bound the laws region is handed: the locks and claim files it states" at=policy:bound/regions by=target form=alphabet measure=coordinates role=reads scope=region/laws value=**/*.bound
154
151
  bound-lock/1 about="the coordinates of bound the packages region is handed: its manifest and its build config" at=policy:bound/regions by=target form=alphabet measure=coordinates role=reads scope=region/packages value=package.json|tsconfig.json
155
- bound-lock/1 about="the coordinates of bound the source region is handed: every module it is made of" at=policy:bound/regions by=target form=alphabet measure=coordinates role=reads scope=region/source value=src/**/*.ts
156
152
  bound-lock/1 about="the coordinates of bound the vocabulary region is handed: the words its modules spell" at=policy:bound/regions by=target form=alphabet measure=coordinates role=reads scope=region/vocabulary value=src/**/*.ts
157
153
  bound-lock/1 about="the digest at zero" at=policy:bound/door by=target form=alphabet measure=text role=writes scope=page/seq/digest value=present
158
154
  bound-lock/1 about="the dist observation, owed by this lane until the folder is cut again" at=witness:a-dist-owed-until-the-folder-is-cut by=target form=alphabet measure=id role=reads scope=observed/dist value=owed
@@ -256,7 +252,7 @@ bound-lock/1 about="the typescript tree world after its released capsule: said,
256
252
  bound-lock/1 about="the vectors a place holds, a row per region, as the fold counts them from its receipts" at=policy:bound/regions by=target form=alphabet measure=receipts role=reads scope=region/vectors value=vector/**
257
253
  bound-lock/1 about="the vectors world saying its own description in its own lock: the regions of 2fb13c6f unchanged, its prose under its key; packed from releases only, topos 0.1.2 and the algebra release, each laid from its digest" at=policy:bound/capsules by=target form=alphabet measure=id role=writes scope=dep/topos-json/own-what shape=dist/index.js value=sha256:5ec73255e3f089e3b9c7bc1d6351a07b8aa302a346fe16ed44cbcb67293c7891
258
254
  bound-lock/1 about="the version the forge tags, read from the manifest" at=policy:bound/pipeline by=target form=alphabet measure=id role=writes scope=pipeline/version value="node -p \"require('./package.json').version\""
259
- bound-lock/1 about="the version this place is bound at" at=policy:bound/011-port-release by=target form=alphabet measure=id role=writes scope=version value=0.1.1
255
+ bound-lock/1 about="the version this place is bound at" at=witness:bound/013-contract-closure by=target form=alphabet measure=id role=writes scope=version value=0.1.3
260
256
  bound-lock/1 about="the writer that vouches each source at the digest a take judged it: a family the wire lists, which bound reads here and never spells" at=policy:bound/store by=target form=alphabet measure=id role=reads scope=store/vouches value=tree
261
257
  bound-lock/1 about="this place is a package: class defaults of package resolve here" at=policy:bound/place by=target form=alphabet measure=id role=writes scope=place/kind value=package
262
258
  bound-lock/1 about="topos-node with a thin manifest: no typesVersions, main or types, dev dependencies only where a line says so, and a bin field from the place bin/<name> lines" at=policy:bound/capsules by=target form=alphabet measure=id role=writes scope=dep/topos-node/thin-manifest shape=dist/index.js value=sha256:da1fd0bb74f72ae95bce5929055c50ccd150d8903ba5560d99df48bf236d8dc0
@@ -265,7 +261,6 @@ bound-lock/1 about="usage, rendered from VERB_NAMES and FLAGS, never written by
265
261
  bound-lock/1 about="views/world resolves into worlds, and a place's bytes are its files, its lock, and the root lines that resolve into it" at=witness:a-world-line-opens-worlds by=target form=alphabet measure=id role=reads scope=bytes/world value=worlds
266
262
  bound-lock/1 about="what a fold refuses, drawn from the first refused case" at=witness:project-coordinates-are-relative by=target form=alphabet measure=id needs=docs/img/refuses.svg role=demands scope=view/figure-refuses shape=docs/img/refuses.svg value=figure-refuses@1
267
263
  bound-lock/1 about="what a place own lock says for the tree under its own name: its prose, and the silent substitutions it bounds in its own code, the place written after the steps the glob names" at=policy:bound/places by=target form=alphabet measure=id role=reads scope=place/speaks-under value=prose/*|hazard/silent-substitution
268
- bound-lock/1 about="what a place's own lock says for the tree: the views it is written by, the readers that read what it holds and the notation it is read in, each path it names read from the place; the roles of what it holds are read from it whole" at=policy:bound/places by=target form=alphabet measure=id role=reads scope=place/speaks value=view/*/**|reader/*/**|notation/*/**
269
264
  bound-lock/1 about="what is open, as cards, one lane at a time: <dir>/ --as card --key lane-X" at=policy:bound/views by=target form=alphabet measure=id role=demands scope=view/card value=cells
270
265
  bound-lock/1 about="what the CLI printed on a sample and the REFUSE a drafted land printed, kept as receipts" at=policy:bound/doi by=target form=alphabet measure=receipts role=reads scope=region/captures value=capture/**|*refused*
271
266
  bound-lock/1 about="what the forge runs to check this place: the suites, then a quiet fold" at=policy:bound/pipeline-published-dependencies by=target form=alphabet measure=id role=writes scope=pipeline/check value="npm test && node dist/cli/verb.js fold --check"
@@ -273,13 +268,11 @@ bound-lock/1 about="what the second head found, which leaves the place has writt
273
268
  bound-lock/1 about="when the apart claim is withdrawn" at=policy:bound/door by=target form=alphabet measure=id role=writes scope=form/style/fold/back value=2.2
274
269
  bound-lock/1 about="where a located world keeps each region file, by directory and extension, so the host loads the tree beside the pin" at=policy:bound/010 by=target form=alphabet measure=id role=writes scope=release/layout value=src/regions:.ts
275
270
  bound-lock/1 about="who signs this place" at=policy:bound/010 by=target form=alphabet measure=id role=writes scope=author value=Lapxo
276
- bound-lock/1 about="{\n \"name\": \"@lapxo/bound\",\n \"version\": \"0.1.1\",\n \"type\": \"module\",\n \"description\": \"Read the bounds you already wrote, fold them, and name the contradictions.\",\n \"main\": \"dist/cli/verb.js\",\n \"types\": \"dist/cli/verb.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/cli/verb.d.ts\",\n \"default\": \"./dist/cli/verb.js\"\n },\n \"./package.json\": \"./package.json\"\n },\n \"scripts\": {\n \"test\": \"node --import ./test/setup.ts --test test/*.test.ts\",\n \"clean\": \"rm -rf dist .tsbuildinfo* tsconfig.tsbuildinfo\",\n \"build\": \"node tools/build.mjs\",\n \"prepack\": \"npm run build\"\n },\n \"files\": [\n \"LICENSE\",\n \"README.md\",\n \"CONTRIBUTING.md\",\n \"dist/**/*\",\n \"TARGET.bound\",\n \"receipts.bound\",\n \"docs/usage.md\",\n \"docs/readme-assets/atlas.svg\"\n ],\n \"author\": \"Lapxo\",\n \"license\": \"MIT\",\n \"dependencies\": {\n \"@lapxo/obligations\": \"0.3.5\",\n \"@lapxo/topos\": \"0.1.8\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^20.0.0\",\n \"typescript\": \"^5.0.0\"\n },\n \"sideEffects\": false,\n \"engines\": {\n \"node\": \">=22.15\"\n },\n \"bin\": {\n \"bound\": \"./dist/cli/verb.js\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/Lapxo/bound.git\"\n },\n \"homepage\": \"https://github.com/Lapxo/bound#readme\",\n \"bugs\": {\n \"url\": \"https://github.com/Lapxo/bound/issues\"\n },\n \"keywords\": [\n \"cli\",\n \"content-addressed\",\n \"provenance\",\n \"receipts\",\n \"constraints\",\n \"reproducible\"\n ]\n}" at=policy:bound/011-port-release by=target form=alphabet measure=text role=writes scope=prose/en/files/manifest shape=package.json value=lock
271
+ bound-lock/1 about="{\n \"name\": \"@lapxo/bound\",\n \"version\": \"0.1.3\",\n \"type\": \"module\",\n \"description\": \"Read the bounds you already wrote, fold them, and name the contradictions.\",\n \"main\": \"dist/cli/verb.js\",\n \"types\": \"dist/cli/verb.d.ts\",\n \"exports\": {\n \".\": {\n \"types\": \"./dist/cli/verb.d.ts\",\n \"default\": \"./dist/cli/verb.js\"\n },\n \"./package.json\": \"./package.json\"\n },\n \"scripts\": {\n \"test\": \"node --import ./test/setup.ts --test test/*.test.ts\",\n \"clean\": \"rm -rf dist .tsbuildinfo* tsconfig.tsbuildinfo\",\n \"build\": \"node tools/build.mjs\",\n \"prepack\": \"npm run build\"\n },\n \"files\": [\n \"LICENSE\",\n \"README.md\",\n \"CONTRIBUTING.md\",\n \"dist/**/*\",\n \"TARGET.bound\",\n \"receipts.bound\",\n \"docs/usage.md\",\n \"docs/readme-assets/atlas.svg\"\n ],\n \"author\": \"Lapxo\",\n \"license\": \"MIT\",\n \"dependencies\": {\n \"@lapxo/obligations\": \"0.3.5\",\n \"@lapxo/topos\": \"0.1.8\"\n },\n \"devDependencies\": {\n \"@types/node\": \"^20.0.0\",\n \"typescript\": \"^5.0.0\"\n },\n \"sideEffects\": false,\n \"engines\": {\n \"node\": \">=22.15\"\n },\n \"bin\": {\n \"bound\": \"./dist/cli/verb.js\"\n },\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/Lapxo/bound.git\"\n },\n \"homepage\": \"https://github.com/Lapxo/bound#readme\",\n \"bugs\": {\n \"url\": \"https://github.com/Lapxo/bound/issues\"\n },\n \"keywords\": [\n \"cli\",\n \"content-addressed\",\n \"provenance\",\n \"receipts\",\n \"constraints\",\n \"reproducible\"\n ]\n}" at=witness:bound/013-manifest-version by=target form=alphabet measure=text role=writes scope=prose/en/files/manifest shape=package.json value=lock
277
272
  bound-lock/1 about="{count} hold" at=policy:bound/010 by=target form=alphabet measure=text role=writes scope=prose/en/badge/held value=lock
278
273
  bound-lock/1 about="{lo}..{hi} · {state}" at=policy:bound/readme by=target form=alphabet measure=text role=writes scope=prose/en/figure/fold/together value=present
279
274
  bound-lock/1 about="{title} (Version {version})" at=policy:bound/010 by=target form=alphabet measure=text role=writes scope=prose/en/cite/versioned value=lock
280
275
  bound-lock/1 about="{who} apart · {state}" at=policy:bound/readme by=target form=alphabet measure=text role=writes scope=prose/en/figure/fold/apart value=present
281
- bound-lock/1 about=Check at=policy:bound/010 by=target form=alphabet measure=text role=writes scope=prose/en/check/heading value=lock
282
- bound-lock/1 about=Check at=policy:bound/prose by=target form=alphabet measure=text role=writes scope=prose/en/check/heading value=lock
283
276
  bound-lock/1 about=DOI at=policy:bound/010 by=target form=alphabet measure=text role=writes scope=prose/en/badge/doi value=lock
284
277
  bound-lock/1 about=bits at=policy:bound/paid by=target form=alphabet measure=text role=writes scope=page/count/capture/bits value=present
285
278
  bound-lock/1 about=bound at=policy:bound/readme by=target form=alphabet measure=id role=writes scope=page/group/title value=present
@@ -354,12 +347,13 @@ bound-lock/1 at=policy:project/local-journals by=target form=alphabet measure=cl
354
347
  bound-lock/1 at=policy:project/local-journals by=target form=alphabet measure=class role=writes scope=keys/reader value=read
355
348
  bound-lock/1 at=policy:prose/render-contract by=target form=alphabet measure=digest role=writes scope=uses/prose-render value=sha256:8b678e37f1fe656a5c456f09c0d57020e3aeddb8acec2b5ffc92054a3ce26c3b
356
349
  bound-lock/1 at=policy:prose/render-contract by=target form=alphabet measure=id role=writes scope=dep/prose-render shape=dist/regions/text.js value=sha256:a1c31a72a31eaf8ea7df0fb98235c4b465aee1d9575aebcc480f09b4bf64daf1
357
- bound-lock/1 at=policy:release/process-captures by=target form=alphabet measure=coordinates role=reads scope=region/captures value=src/**|test/**|vectors/process/**|dist/**|package.json|TARGET.bound
358
- bound-lock/1 at=policy:release/process-inputs by=target form=alphabet measure=coordinates role=reads scope=region/vectors value=src/**|test/**|vectors/process/**|dist/**|package.json|TARGET.bound
350
+ bound-lock/1 at=policy:public-artifact-source by=target form=alphabet measure=id role=writes scope=sources/obligations value=https://registry.npmjs.org/@lapxo/obligations/-/obligations-0.3.5.tgz
351
+ bound-lock/1 at=policy:public-artifact-source by=target form=alphabet measure=id role=writes scope=sources/topos value=https://registry.npmjs.org/@lapxo/topos/-/topos-0.1.8.tgz
352
+ bound-lock/1 at=policy:public-artifact-source by=target form=alphabet measure=id role=writes scope=sources/typescript value=https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz
359
353
  bound-lock/1 at=policy:rendering by=target form=alphabet measure=digest role=writes scope=uses/rendering value=sha256:0f7653b659f372fd7bc1b140d0103606ebd8814dee8ef3be30949c6647b482a3
360
354
  bound-lock/1 at=policy:selected-wire by=target form=alphabet measure=digest role=writes scope=uses/wire value=sha256:a717c60945add1facfd822b3ee2da73a8fa2f781dd0aa2de9be778e66b8f3591
361
- bound-lock/1 at=policy:source/coordinates by=target form=alphabet measure=coordinates role=reads scope=region/source value=src/**
362
- bound-lock/1 at=policy:source/parser by=target form=alphabet measure=digest needs=node_modules/typescript role=reads scope=dep/typescript value=sha256:9dc6ee1f8dec9598cbecff5e2fcf2b916d04294c0615588dd424448a5a3afce5
355
+ bound-lock/1 at=witness:admission-samples-are-inputs by=target form=alphabet measure=coordinates role=reads scope=region/captures value=src/**|test/**|vectors/process/**|dist/**|package.json|TARGET.bound|samples/admission/**
356
+ bound-lock/1 at=witness:admission-samples-are-inputs by=target form=alphabet measure=coordinates role=reads scope=region/vectors value=src/**|test/**|vectors/process/**|dist/**|package.json|TARGET.bound|samples/admission/**
363
357
  bound-lock/1 at=witness:explicit-executable-candidate by=target form=alphabet measure=id role=writes scope=dep/topos-doc/born shape=dist/index.js value=sha256:2c507e7fe764e55d08fc27b01567d462060fe50d1896d19866f831a823852ae5
364
358
  bound-lock/1 at=witness:process-captures-reader by=target form=alphabet measure=digest role=writes scope=dep/process-readings shape=dist/regions/vectors.js value=sha256:b22d161e8ae7c96789c7fb038b1d755fa149203c7e035c6d2fa5f2a164fecb54
365
359
  bound-lock/1 at=witness:process-captures-selection by=target form=alphabet measure=digest role=writes scope=uses/process-readings value=sha256:a55f6849b39e77b6fcd89b2a2ba8fa286a7c8626193bbfe14aef30bef6b29458
@@ -376,5 +370,6 @@ bound-lock/1 at=witness:project-coordinates-are-relative by=target form=interval
376
370
  bound-lock/1 at=witness:project-coordinates-are-relative by=target form=interval measure=count needs=samples/literal/yes/key.json|samples/literal/no/key.json role=reads scope=literal/key value=0..0
377
371
  bound-lock/1 at=witness:project-coordinates-are-relative by=target form=interval measure=count needs=samples/literal/yes/org.json|samples/literal/no/org.json role=reads scope=literal/org value=0..0
378
372
  bound-lock/1 at=witness:project-coordinates-are-relative by=target form=interval measure=count needs=samples/literal/yes/path.json|samples/literal/no/path.json role=reads scope=literal/path value=0..0
373
+ bound-lock/1 at=witness:public-artifact-closure by=target form=alphabet measure=digest needs=node_modules/typescript role=reads scope=dep/typescript shape=package value=sha256:10e108c9cf7d5f2879053dff18515fb405abf2ccef63eaaf017d9c571687a1d3
379
374
  bound-lock/1 at=witness:source-reader by=target form=alphabet measure=digest role=writes scope=dep/source-readings shape=dist/regions/source.js value=sha256:5ccae33a9e99f6acba690d9664e22cfe818205841e4e9d3083d4b24fb68d7075
380
375
  bound-lock/1 at=witness:source-selection by=target form=alphabet measure=digest role=writes scope=uses/source-readings value=sha256:4129c469d3296a1fcda640f59e0d7c37f255b1e88c2f8e2447cd1359a514f356
@@ -1,3 +1,4 @@
1
+ import { withoutEffects } from "../host/read-only.js";
1
2
  import { selectedWireAt } from "../host/selected-topos.js";
2
3
  import { timing } from "../host/timing.js";
3
4
  import { admittedObjects } from "../host/objects.js";
@@ -22,7 +23,7 @@ import { ownLock, ownRoot, ownStore } from "../observe/runner.js";
22
23
  import { widenedWithoutWitness } from "../judge/widening.js";
23
24
  import { retractedTogether, withdrawal } from "../land/withdraw.js";
24
25
  import { isCeiling } from "../fold/configures.js";
25
- import { inertIn, rankedIn } from "../land/inert.js";
26
+ import { inertIn } from "../land/inert.js";
26
27
  import { landSaid } from "./pass.js";
27
28
  import { underTheLock, underTheRegions } from "../land/act.js";
28
29
  import { openedBy } from "../fold/opened.js";
@@ -39,6 +40,68 @@ import { ownerOf, epochOf, forksItself, forksJoinedBy, renderInto, sideOf, signe
39
40
  import { claimLinesIn, entryOf, namedPlaces, placesOfFile, positionalsOf, valueOf } from "./args.js";
40
41
  import { FLAGS } from "./names.js";
41
42
  import { judgeDemand, landTake } from "./takes.js";
43
+ /** Admission reads an existing evidence snapshot; acquiring observations belongs to fold. */
44
+ function admissionEvidence(root, signed, standing) {
45
+ return withoutEffects(() => {
46
+ const store = storeOf(root);
47
+ const reads = !signed.some(isReaderLock) && signed.some(isCeiling);
48
+ const fold = reads ? coneOf(root, entryOf(root), signed.filter(isCeiling)) : { standing, observed: [], own: {}, kept: true };
49
+ const inert = inertIn(signed, { standing: fold.standing, ceiling: isCeiling, signsReader: !reads, observed: fold.observed, own: fold.own, epoch: epochOf([...lockLines(store), ...signed]) });
50
+ if (inert.length)
51
+ throw Error('REFUSE·inert no existing reading meets these ceilings; fold the declared inputs first');
52
+ const coordinates = coordinatesWithdrawn(root, store, signed, [...signed, ...standing]);
53
+ if (coordinates.refused.length)
54
+ throw Error(`REFUSE·withdraw a file is still read: ${coordinates.refused.map(one => `${one.path} by ${one.by}`).join(' · ')}`);
55
+ const view = `${wordOf(standing, 'families', 'view')}/`;
56
+ const judged = signed.filter(one => isConfig(one) && fieldOf(one, 'role') === 'demands' && fieldOf(one, 'value') !== 'withdraw' && !fieldOf(one, 'scope').startsWith(view)).map(one => judgeDemand(root, one));
57
+ const refused = judged.filter(one => one.kind === 'refused' || (one.kind === 'judged' && one.verdict.kind === 'refuse'));
58
+ if (refused.length)
59
+ throw Error(`REFUSE·takes ${refused.map(one => `${one.scope}: ${one.kind === 'judged' ? one.verdict.why : one.kind === 'refused' ? one.why : ''}`).join(' · ')}`);
60
+ return { coordinates, judged, fold };
61
+ });
62
+ }
63
+ /** One witness rule for prospective admission and the locked act. */
64
+ function refusesWidening(signed, standing) {
65
+ const widened = widenedWithoutWitness(signed, standing, isCeiling, wordOf(standing, 'at-classes', 'witness'));
66
+ if (widened.length) {
67
+ for (const line of widened)
68
+ process.stderr.write(`WIDER ${fieldOf(line, 'scope')} · ${fieldOf(line, 'value')}\n`);
69
+ process.stderr.write(`${selfName()}: REFUSE·widening nothing landed · a ceiling that admits more than it did gives back freedom the lock had spent: sign it under a witness that names what made the old ceiling wrong\n`);
70
+ return true;
71
+ }
72
+ return false;
73
+ }
74
+ /** Admission-only checks; observation and mutation belong to the later act. */
75
+ function refusesAdmission(root, signed, standing) {
76
+ if (refusesWidening(signed, standing))
77
+ return true;
78
+ const store = storeOf(root);
79
+ const outside = outsideTheirCapsule(root, standing, signed);
80
+ for (const line of outside)
81
+ process.stderr.write(`OUTSIDE ${fieldOf(line, 'scope')} · ${fieldOf(line, 'value')}\n`);
82
+ if (outside.length) {
83
+ process.stderr.write(`${selfName()}: REFUSE·outside nothing landed · a reader or a capsule lands in the capsule of its world, never in the wire or the instrument\n`);
84
+ return true;
85
+ }
86
+ const forks = forksJoinedBy(store, signed).filter(fork => forksItself(fork.lines) || fork.lines.filter(line => signed.includes(line)).length > 1);
87
+ if (forks.length) {
88
+ for (const fork of forks) {
89
+ process.stderr.write(`FORK ${fork.key}\n`);
90
+ for (const line of fork.lines)
91
+ process.stderr.write(` ${signed.includes(line) ? 'new ' : 'held'} by=${fieldOf(line, 'by')} ${sideOf(line, lockLines(store))}\n`);
92
+ }
93
+ process.stderr.write(`${selfName()}: REFUSE·fork nothing landed · one signer never forks itself, and one act never forks itself: withdraw the held side in this batch, or sign the other reading under its own scope\n`);
94
+ return true;
95
+ }
96
+ const held = aliasPinned(root, signed);
97
+ if (held.length) {
98
+ for (const one of held)
99
+ process.stderr.write(`ALIAS ${one.scope} · ${one.digest} · ${one.by.join(' ')}\n`);
100
+ process.stderr.write(`${selfName()}: REFUSE·alias nothing landed · an alias is withdrawn only when no pin names its digest\n`);
101
+ return true;
102
+ }
103
+ return false;
104
+ }
42
105
  /** A batch signed as accept signs it: at the next epoch, by the key it names, each line read by the wire it would land under. */
43
106
  export async function signedBatch(root, args, files) {
44
107
  const byFile = files.map((file) => [placesOfFile(file), claimLinesIn(file)]);
@@ -57,10 +120,16 @@ export async function signedBatch(root, args, files) {
57
120
  }
58
121
  const store = storeOf(root);
59
122
  const destinations = [...new Set([...namedPlaces(args), ...byFile.flatMap(([heads]) => heads)])];
123
+ const rooted = lockStanding(store);
124
+ const stray = destinations.filter(one => !placesIn(root).includes(one) && !foundableIn(root, rooted, wordOf(rooted, 'families', 'region'), one));
125
+ if (stray.length) {
126
+ process.stderr.write(`${selfName()}: REFUSE·place nothing landed · ${stray.join(', ')} is no place of the tree, and no line of the root founds it: --place names the own lock a line lands in\n`);
127
+ return { kind: 'exit', code: 2 };
128
+ }
60
129
  const epoch = epochOf([...lockLines(store), ...destinations.flatMap((place) => appliedIn(root, place))]) + 1;
61
130
  const stale = proposed.filter((line) => fieldOf(line, 'sig')).flatMap((line) => ((by, key) => (key === undefined || !verifiesOwnerLine(line, key, signaturesOf(store).admitted)
62
131
  ? [`${fieldOf(line, 'scope')} · the signature does not hold for ${by || '∅'}`]
63
- : isConfig(line) && Number(fieldOf(line, 'epoch')) !== epoch ? [`${fieldOf(line, 'scope')} · signed by ${by} at epoch ${fieldOf(line, 'epoch')}, and this act lands at ${epoch}: sign it again`] : []))(fieldOf(line, 'by'), publicKeyOf(root, fieldOf(line, 'by'))));
132
+ : []))(fieldOf(line, 'by'), publicKeyOf(root, fieldOf(line, 'by'))));
64
133
  if (stale.length) {
65
134
  for (const one of stale)
66
135
  process.stderr.write(`SIGNED ${one}\n`);
@@ -85,6 +154,13 @@ export async function signedBatch(root, args, files) {
85
154
  }
86
155
  return { kind: 'once', proposed };
87
156
  }
157
+ const outdated = proposed.filter(line => fieldOf(line, 'sig') && isConfig(line) && Number(fieldOf(line, 'epoch')) !== epoch);
158
+ if (outdated.length) {
159
+ for (const line of outdated)
160
+ process.stderr.write(`SIGNED ${fieldOf(line, 'scope')} · signed by ${fieldOf(line, 'by')} at epoch ${fieldOf(line, 'epoch')}, and this act lands at ${epoch}: sign it again\n`);
161
+ process.stderr.write(`${selfName()}: REFUSE·signed nothing landed · ${outdated.length} lines a lane signed stand at no epoch this act\n`);
162
+ return { kind: 'exit', code: 1 };
163
+ }
88
164
  const keyFile = valueOf(args, FLAGS.keyFile);
89
165
  const keyId = valueOf(args, FLAGS.key) ?? ownerOf(store);
90
166
  const publicKey = drafted.length ? publicKeyOf(root, keyId) : undefined;
@@ -121,6 +197,15 @@ export async function signedBatch(root, args, files) {
121
197
  return { kind: 'exit', code: 1 };
122
198
  }
123
199
  }
200
+ const taken = retractedTogether(signed);
201
+ if (taken.length) {
202
+ for (const pair of taken) {
203
+ process.stderr.write(`WITHDRAW ${sayingOf(pair.withdraw)}\n`);
204
+ process.stderr.write(`NEW ${sayingOf(pair.line)}\n`);
205
+ }
206
+ process.stderr.write(`${selfName()}: REFUSE·withdraw nothing landed · a withdraw matches a new line in this batch, so it takes that line too\n`);
207
+ return { kind: 'exit', code: 1 };
208
+ }
124
209
  const authority = authorityFor([...lockLines(store), ...signed], rootSigner(root), signaturesOf(store).admitted);
125
210
  const ownership = ownedOf(root, signed, line => authority.of(line).kind === 'admitted', by => authority.admitted.find(one => one.id === by)?.coverage ?? [], named, line => { const heads = byFile.flatMap(([heads, lines]) => lines.some(draft => sayingOf(draft) === sayingOf(line)) ? heads : []); return heads.length ? heads : undefined; });
126
211
  if (ownership.nowhere.length) {
@@ -128,6 +213,14 @@ export async function signedBatch(root, args, files) {
128
213
  process.stderr.write(`${selfName()}: REFUSE·signer ${keyId} uncovered ${fieldOf(line, 'scope')} · nothing signed for delivery\n`);
129
214
  return { kind: 'exit', code: 1 };
130
215
  }
216
+ if (ownership.forked.length) {
217
+ for (const one of ownership.forked)
218
+ process.stderr.write(`FORKED ${one.place}/${LOCK} · ${one.key} · the lot would leave two standing claims where the lock had one\n`);
219
+ process.stderr.write(`${selfName()}: REFUSE·forked nothing landed · ${ownership.forked.length} claims an own lot would fork: a lot for many places lands the same lines at each, and lines meant for one place go in a lot of their own\n`);
220
+ return { kind: 'exit', code: 1 };
221
+ }
222
+ if (refusesAdmission(root, ownership.ledger, lockStanding(store)))
223
+ return { kind: 'exit', code: 1 };
131
224
  if (signed.some(line => !isConfig(line))) {
132
225
  try {
133
226
  await admittedObjects(root, [...signedOwnerLines(store), ...signed], text => process.stdout.write(text + '\n'));
@@ -137,6 +230,13 @@ export async function signedBatch(root, args, files) {
137
230
  return { kind: 'exit', code: 1 };
138
231
  }
139
232
  }
233
+ try {
234
+ admissionEvidence(root, ownership.ledger, lockStanding(store));
235
+ }
236
+ catch (error) {
237
+ process.stderr.write(`${selfName()}: ${error instanceof Error ? error.message : String(error)} · nothing landed\n`);
238
+ return { kind: 'exit', code: 1 };
239
+ }
140
240
  return { kind: 'signed', signed, epoch, ...(drafted.length ? { keyId } : {}) };
141
241
  }
142
242
  /**
@@ -151,26 +251,11 @@ export async function accept(root, args) {
151
251
  const started = Date.now();
152
252
  const named = namedPlaces(args);
153
253
  const placed = positionalsOf(args).reduce((all, file) => ((places) => (places.length ? claimLinesIn(file).reduce((got, line) => got.set(sayingOf(line), [...new Set([...(got.get(sayingOf(line)) ?? []), ...places])]), all) : all))(placesOfFile(file)), new Map());
154
- const rooted = lockStanding(storeOf(root));
155
- const stray = [...new Set([...named, ...[...placed.values()].flat()])].filter((one) => !placesIn(root).includes(one) && !foundableIn(root, rooted, wordOf(rooted, 'families', 'region'), one));
156
- if (stray.length) {
157
- process.stderr.write(`${selfName()}: REFUSE·place nothing landed · ${stray.join(', ')} is no place of the tree, and no line of the root founds it: --place names the own lock a line lands in\n`);
158
- return 2;
159
- }
160
254
  const batch = await signedBatch(root, args, positionalsOf(args));
161
255
  if (batch.kind === 'exit')
162
256
  return batch.code;
163
257
  if (batch.kind === 'once')
164
258
  return underTheLock(storeOf(root), 'land', () => repaid(root, batch.proposed, started));
165
- const taken = retractedTogether(batch.signed);
166
- if (taken.length) {
167
- for (const pair of taken) {
168
- process.stderr.write(`WITHDRAW ${sayingOf(pair.withdraw)}\n`);
169
- process.stderr.write(`NEW ${sayingOf(pair.line)}\n`);
170
- }
171
- process.stderr.write(`${selfName()}: REFUSE·withdraw nothing landed · a withdraw matches a new line in this batch, so it takes that line too\n`);
172
- return 1;
173
- }
174
259
  const store = storeOf(root);
175
260
  const authority = authorityFor([...lockLines(store), ...batch.signed], rootSigner(root), signaturesOf(store).admitted);
176
261
  const owned = ownedOf(root, batch.signed, (line) => authority.of(line).kind === 'admitted', (by) => authority.admitted.find((one) => one.id === by)?.coverage ?? [], named, (line) => placed.get(sayingOf(line)));
@@ -180,12 +265,6 @@ export async function accept(root, args) {
180
265
  process.stderr.write(`${selfName()}: REFUSE·nowhere nothing landed · ${owned.nowhere.length} lines neither the ledger nor an own lock admits: a line is landed signed where it stands, never written in bare\n`);
181
266
  return 1;
182
267
  }
183
- if (owned.forked.length) {
184
- for (const one of owned.forked)
185
- process.stderr.write(`FORKED ${one.place}/${LOCK} · ${one.key} · the lot would leave two standing claims where the lock had one\n`);
186
- process.stderr.write(`${selfName()}: REFUSE·forked nothing landed · ${owned.forked.length} claims an own lot would fork: a lot for many places lands the same lines at each, and lines meant for one place go in a lot of their own\n`);
187
- return 1;
188
- }
189
268
  const places = placesIn(root);
190
269
  const pinned = [...new Set([...owned.own.keys(), ...named])];
191
270
  const touched = pinned.length === 0 && owned.ledger.length ? touchedBy(root, places, owned.ledger, lockStanding(store)) : [];
@@ -213,42 +292,18 @@ async function acceptNow(root, batch, started, owned, asked = started, descent =
213
292
  return 1;
214
293
  }
215
294
  }
216
- const reads = !signed.some(isReaderLock) && signed.some(isCeiling);
217
- const fold = reads ? took('cone', () => coneOf(root, entryOf(root), signed.filter(isCeiling), descent)) : { ...took('lines', () => standingOf(root)), observed: [], own: {}, kept: true };
218
- const inert = inertIn(signed, { standing: fold.standing, ceiling: isCeiling, signsReader: !reads, observed: fold.observed, own: fold.own, epoch });
219
- const widened = widenedWithoutWitness(signed, fold.standing, isCeiling, wordOf(fold.standing, 'at-classes', 'witness'));
220
- if (widened.length) {
221
- for (const line of widened)
222
- process.stderr.write(`WIDER ${fieldOf(line, 'scope')} · ${fieldOf(line, 'value')}\n`);
223
- process.stderr.write(`${selfName()}: REFUSE·widening nothing landed · a ceiling that admits more than it did gives back freedom the lock had spent: sign it under a witness that names what made the old ceiling wrong\n`);
224
- return 1;
225
- }
226
- if (inert.length) {
227
- for (const line of inert)
228
- process.stderr.write(`INERT ${fieldOf(line, 'scope')} measure=${fieldOf(line, 'measure')}\n`);
229
- process.stderr.write(`${selfName()}: REFUSE·inert nothing landed · no reading meets these ceilings: sign the reader that meets them in this batch, or leave them out\n`);
295
+ const snapshot = took('lines', () => standingOf(root));
296
+ if (refusesAdmission(root, signed, snapshot.standing))
230
297
  return 1;
298
+ let evidence;
299
+ try {
300
+ evidence = took('evidence', () => admissionEvidence(root, signed, snapshot.standing));
231
301
  }
232
- const outside = outsideTheirCapsule(root, fold.standing, signed);
233
- for (const line of outside)
234
- process.stderr.write(`OUTSIDE ${fieldOf(line, 'scope')} · ${fieldOf(line, 'value')}\n`);
235
- if (outside.length) {
236
- process.stderr.write(`${selfName()}: REFUSE·outside nothing landed · a reader or a capsule lands in the capsule of its world, never in the wire or the instrument\n`);
302
+ catch (error) {
303
+ process.stderr.write(`${selfName()}: ${error instanceof Error ? error.message : String(error)} · nothing landed\n`);
237
304
  return 1;
238
305
  }
239
- const forks = took('fork', () => forksJoinedBy(store, signed).filter((fork) => forksItself(fork.lines) || fork.lines.filter((line) => signed.includes(line)).length > 1));
240
- if (forks.length) {
241
- for (const fork of forks) {
242
- process.stderr.write(`FORK ${fork.key}\n`);
243
- for (const line of fork.lines)
244
- process.stderr.write(` ${signed.includes(line) ? 'new ' : 'held'} by=${fieldOf(line, 'by')} ${sideOf(line, lockLines(store))}\n`);
245
- }
246
- process.stderr.write(`${selfName()}: REFUSE·fork nothing landed · one signer never forks itself, and one act never forks itself: withdraw the held side in this batch, or sign the other reading under its own scope\n`);
247
- return 1;
248
- }
249
- if (reads)
250
- for (const line of rankedIn(signed.filter(isCeiling), fold))
251
- process.stdout.write(`${line}\n`);
306
+ const fold = evidence.fold;
252
307
  const authority = took('authority', () => authorityFor([...lockLines(store), ...signed], rootSigner(root), signaturesOf(store).admitted));
253
308
  for (const line of signed) {
254
309
  const verdict = authority.of(line);
@@ -257,14 +312,8 @@ async function acceptNow(root, batch, started, owned, asked = started, descent =
257
312
  return 1;
258
313
  }
259
314
  }
260
- const coordinates = coordinatesWithdrawn(root, store, signed, [...signed, ...fold.standing]);
261
- if (coordinates.refused.length) {
262
- for (const one of coordinates.refused)
263
- process.stderr.write(`RUN ${one.path} · still read by ${one.by}\n`);
264
- process.stderr.write(`${selfName()}: REFUSE·withdraw nothing landed · a file a block still reads cannot leave: withdraw what reads it first, or keep it\n`);
265
- return 1;
266
- }
267
315
  const withdraws = signed.filter((line) => fieldOf(line, 'value') === 'withdraw');
316
+ const coordinates = evidence.coordinates;
268
317
  const withdrawn = withdraws.length ? withdrawal(withdraws, lockLines(store)) : null;
269
318
  const view = `${wordOf(fold.standing, 'families', 'view')}/`;
270
319
  const lot = fullDigest(store, signed);
@@ -272,8 +321,7 @@ async function acceptNow(root, batch, started, owned, asked = started, descent =
272
321
  ? fullDigest(store, [...vouchedCoordinates(store)].sort(([a], [b]) => a.localeCompare(b)).map(([coordinate, digest]) => `${coordinate} ${digest}`))
273
322
  : 'key' in fold && typeof fold.key === 'string' ? fold.key : fullDigest(store, fold.standing);
274
323
  const cached = keptTakes(keptVerdict(store, tree, lot));
275
- const judged = cached ?? took('takes', () => signed.filter((one) => fieldOf(one, 'role') === 'demands' && fieldOf(one, 'value') !== 'withdraw' && !fieldOf(one, 'scope').startsWith(view))
276
- .map((line) => judgeDemand(root, line)));
324
+ const judged = cached ?? evidence.judged;
277
325
  if (!cached)
278
326
  keepVerdict(store, tree, lot, judged.map(packTake));
279
327
  const refused = judged.filter((one) => one.kind === 'refused' || (one.kind === 'judged' && one.verdict.kind === 'refuse'));
@@ -283,13 +331,6 @@ async function acceptNow(root, batch, started, owned, asked = started, descent =
283
331
  process.stderr.write(`${selfName()}: REFUSE·takes nothing landed · ${refused.length} takes refused: a batch lands whole or not at all\n`);
284
332
  return 1;
285
333
  }
286
- const held = aliasPinned(root, signed);
287
- if (held.length) {
288
- for (const one of held)
289
- process.stderr.write(`ALIAS ${one.scope} · ${one.digest} · ${one.by.join(' ')}\n`);
290
- process.stderr.write(`${selfName()}: REFUSE·alias nothing landed · an alias is withdrawn only when no pin names its digest\n`);
291
- return 1;
292
- }
293
334
  const told = took('told', () => standingOf(root, undefined, signed));
294
335
  for (const name of openedBy(descent, told.standing, signed))
295
336
  process.stderr.write(`OPEN ${name}\n`);
package/dist/cli/owner.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { readOnly } from "../host/read-only.js";
1
2
  import { keyFor, lockLines, releaseOf, signedLockLines } from "../fold/keys.js";
2
3
  import { existsSync, renameSync, writeFileSync } from 'node:fs';
3
4
  import { join, resolve } from 'node:path';
@@ -83,6 +84,8 @@ export function renderedLock(root, store, told = []) {
83
84
  return readRelease(released);
84
85
  const owned = [...lockLines(store), ...told];
85
86
  const render = () => renderTarget(owned, undefined, authorityFor(owned, rootSigner(root), signaturesOf(store, told).admitted).of, forkReceipts(store, owned));
87
+ if (readOnly())
88
+ return render();
86
89
  if (told.length) {
87
90
  const byLock = rendered.get(lockLines(store)) ?? rendered.set(lockLines(store), new Map()).get(lockLines(store));
88
91
  return byLock.get(told) ?? byLock.set(told, render()).get(told);