Skip to content

Releases: wighawag/pinnace

pinnace@0.18.0

Choose a tag to compare

@github-actions github-actions released this 13 Sep 20:18
e3b898a

Minor Changes

  • 125026c: Add pinnace set <id> — change a live site's metadata (mode, ensName, keep) without rebuilding or re-placing its content

    A site already deployed in ipfs mode can now be promoted to ipns (or get an ensName, or a retention policy) in one command, using the content CID the nodes already hold:

    pinnace set --set-mode ipns --set-ens-name mysite.eth mysite

    It reads the site's current content CID from MFS, writes the resolved metadata.json, applies the site's keep policy, and in ipns mode publishes the IPNS record pointing at that existing CID. No CAR is built and no content is imported or re-placed. The same mode/ensName/keep flags as deploy and pin apply, resolved the same way (stated > stored > default; omitting preserves).

    Because it places no content, it is stricter than deploy about what it will infer: it REFUSES when the publisher does not hold the site, rather than resolving a preserved mode to the ipfs default and writing that demotion to the nodes that do hold it. It also reports the publisher's CID (the one the name resolves to) and names any node holding a different build, since set cannot fix that.

    Also adds readSiteContentCidForWrite / SiteContentUnreadableError to the library surface: the content-side twin of the existing strict metadata read, so a node that will not answer is never reported as a node that does not have the site.

pinnace@0.17.0

Choose a tag to compare

@github-actions github-actions released this 01 Aug 17:21

Minor Changes

  • 26a79a6: Dashboard: copy-to-clipboard buttons on opaque identifiers + ENS field grouping.

    • Each opaque identifier (content CID, IPNS) now has a one-tap copy button beside it. The page gains a tiny progressive-enhancement <script> (the only client-side JS); without JS the values stay fully visible, selectable, and linked — nothing is lost.
    • The four ENS-specific fields (ens name, eth.limo, origin, freshness) are now grouped under a small ENS subheading, separate from the core fields (content cid, ipns, sequence, mode, announced, gateway).
    • The ENS group is shown only when the site resolves an ENS name to warm (ensNameToWarm). A site that opts out, resolves no name, or comes from a report path that does no resolving shows no ENS group — the four fields are all not-applicable, so showing them was noise without signal.

pinnace@0.16.0

Choose a tag to compare

@github-actions github-actions released this 31 Jul 13:58
9ff4676

Minor Changes

  • 5bfa2f7: Redesign the node status dashboard as centered per-site cards with full, readable identifiers.

    The old page was one eleven-column table: the long nowrap cells pushed it past its centered column (so it read as off-centre), and the opaque cid/ipns identifiers had to be middle-elided to fit their cramped cells — hard to read and to compare across nodes. Each site is now a card on its own, so:

    • the page is centered and never overflows its column;
    • every opaque id (cid, ipns, the node peerId, and the origin/freshness paths) gets its own line at the card's full width and is shown in full (no elision), as an inverted-background badge that folds with overflow-wrap: anywhere instead of shredding into a one-character-per-line ribbon;
    • a light/dark color scheme keeps the inverted id badge legible in both modes.

    The full values still survive in the link href/title and in status.json, so showing them loses nothing. The honesty rules are unchanged: a check that could not run still renders the neutral unknown (<reason>), never the red negative, and an absent verdict still reads none.

  • a112f31: Print the publish recipe in the CI summary: promote-to / install-ci --promote-to <id>.

    An ipfs-mode staging pipeline ends with two steps no tool can take for you: promote the build to the live site, then move the ENS contenthash. They are easy to get wrong a week later, and the job summary is exactly where someone is already looking, so it now spells them out with the ids and the cid filled in:

    This is a staging build: mandalas.eth still serves whatever it served before.
    To publish it, in this order:

    pnpm pinnace pin --from-site mandalas-staging --as mandalas.eth --endpoint https://ipfs-publisher.example.com
    1. set the ENS contenthash of mandalas.eth to ipfs://bafy...

    The composite action gains a promote-to input and install-ci a --promote-to <id> flag that bakes it in. It changes no behaviour, only the summary. The order is stated too, because it matters: promoting first means the node is already serving and warming that cid when the record moves, and the previous build stays pinned throughout, so nothing goes dark in between.

    Refused up-front: --promote-to with --set-mode ipns (an ipns deploy re-signs its own name, so there is nothing to promote), and a promotion target equal to the site being deployed.

pinnace@0.15.0

Choose a tag to compare

@github-actions github-actions released this 31 Jul 13:21
0ec4706

Minor Changes

  • c5576d9: Add pin --from-site <id>: promote a staging site's current build to the live site.

    A site wrapper means "the cid this name resolves to". In ipfs mode, where every build has its own address and the ENS record only moves when a human moves it, a pipeline that deploys straight to the LIVE id therefore breaks that meaning on every push: the box warms a cid nobody resolves, status reports freshness=stale for ever, and the cid the record actually points at becomes UNTRACKED, which would let any future retention policy reclaim the live build.

    The fix is a third pin source. CI deploys to a staging id, and a human promotes when they are happy:

    pinnace deploy ./web/build mandalas-staging                   # CI, every push
    pinnace pin --from-site mandalas-staging --as mandalas.eth    # you, when ready
    • The source cid is read from MFS on ONE node and that single cid is fanned out, so an unevenly-landed deploy can never promote two different builds to two boxes. The publisher is read first (its view is the authoritative one, since it is the node that signs names), with the other targets as a reachability fallback so a down publisher cannot block a promotion. The node that answered is reported.
    • The destination keeps its OWN metadata: an omitted --set-mode / --set-ens-name preserves what the DESTINATION stores, never what the source stores, so promoting a plain ipfs staging build into a published ipns site keeps signing its name.
    • Promoting a site onto itself is refused (a no-op dressed as a promotion, and usually a typo for the other id), as is giving more than one source: the cid / --from-ipns / --from-site choice is now enumerated rather than checked pairwise, so a future fourth source cannot create an unchecked combination.
    • Rollback falls out for free: pinnace pin <old-cid> --as <id> with a cid you already know.

    readSiteContentCid is exported from the site-management module as the seam this reads through.

  • 64f4255: Remember superseded builds, and add opt-in retention: --set-keep <n> and a prune verb.

    deploy and pin replaced a site's content and pinned the new root, but never accounted for the old one. Every re-deploy left an ORPHAN pin: content the node keeps for ever, that no site references, that status cannot see, and that no pinnace verb could reclaim (the only handle left was a raw Kubo pin/rm). In ipfs mode that is one orphan per push.

    The fix is deliberately two halves, with different defaults:

    • REMEMBERING is automatic. A write that changes a site's content records the superseded cid in that site's own metadata.json (history, most recent first), which is what makes it accountable at all, and makes rollback a normal command: pinnace pin <old-cid> --as <id>.
    • FORGETTING is opt-in, per site: --set-keep <n> / --unset-keep on deploy and pin, stored in the metadata and PRESERVED when omitted, exactly like --set-mode and --set-ens-name. An absent policy means KEEP EVERYTHING, because pinnace cannot read an ENS record: it knows what a gateway served, never what a contenthash says, so it can never prove an old cid is unreferenced, and a default retention would eventually unpin a live site.

    The new pinnace prune <id> [--keep <n>] [--apply] [--host <name>] applies a policy on its own, across every node. It is a DRY RUN until --apply, and the dry run performs every read and every check, so its report is what a real run would do rather than an estimate. A site with no stored policy and no --keep is refused, not guessed.

    Two invariants hold on every path:

    • Nothing is unpinned that another site currently resolves to. A Kubo recursive pin is not reference-counted, and sites SHARE cids routinely (promoting a staging build with pin --from-site leaves two sites on one cid; a rollback re-points a site at a cid still in its own history), so the guard reads every site's current content before unpinning anything, and refuses outright if it cannot list them. A cid skipped this way is reported and stays listed, because it is still held.
    • A cid leaves the history only once it has actually been unpinned, so a failed unpin is retried by the next prune instead of being forgotten while still occupying disk.

    Unpinning only makes blocks eligible for collection; the space comes back on Kubo's own repo gc, which pinnace still never triggers on your behalf. The verb says so rather than leaving you wondering why the disk did not move.

    Two behaviour changes worth knowing about, both in the write path: a placement now reads the site's current content cid first (that is the superseded cid), and a fully-stated write (--set-mode plus --set-ens-name/--unset-ens-name) now also makes ONE tolerant metadata read to carry keep/history forward. That read cannot refuse a write: neither field is an addressing decision, and losing keep fails safe (back to keeping everything), so the existing "a fully-stated write gets past a node whose MFS cannot be read" property is preserved.

pinnace@0.14.0

Choose a tag to compare

@github-actions github-actions released this 31 Jul 11:52
4e24ead

Minor Changes

  • 39f7c89: Make install-ci emit a pipeline that actually deploys: nodes as args, a shared composite action, and deploy --json.

    The generated GitHub workflow could not run. Its deploy step set IPFS_API, IPFS_TOKEN, SITE_NAME and SITE_MODE and then called pinnace deploy, which reads none of the first three: hosts come from --endpoint / pinnace.json / PINNACE_HOST_<NAME>_ENDPOINT and tokens from PINNACE_HOST_<NAME>_TOKEN. Every emitted pipeline would have died on its first run with "no hosts", and $SITE_NAME expanded empty so the site-id positional vanished too. Its job summary read steps.deploy.outputs.cid, which nothing wrote. It stayed invisible because the emitter is pure and snapshot-tested as a string, so the snapshot only ever compared to itself: see work/notes/findings/install-ci-emits-a-workflow-the-cli-cannot-execute.md.

    • INFRASTRUCTURE IS ARGS, NOT ENV. The emitted step bakes your nodes in as literal --endpoint / --replica-endpoint args and the site as a literal positional, so the only repo secrets are the bearer tokens, named by the CLI's ordinary PINNACE_HOST_<NAME>_TOKEN rule. There is no CI-only env contract: a generated pipeline speaks exactly the surface you speak at your own shell. Emitting with no --endpoint defers to a committed pinnace.json and says so.
    • NEW GLOBAL FLAG --replica-endpoint <url> (repeatable, publisher-first, only alongside --endpoint), so a whole multi-node setup is expressible with args and no config file. Replicas are numbered replica-1, replica-2, ... in the order given, which is what names their env-only tokens. This is what makes args-only CI safe: content redundancy comes from the deploy/pin fan-out, and a replica's mirror timer replicates the signed record and never the content, so a node a run leaves out keeps serving the previous CID. A bare flag, a duplicated replica url, or replicas with no publisher are loud refusals.
    • NEW COMPOSITE ACTION wighawag/pinnace/actions/deploy, which both emit targets uses:. It owns the pinnace deploy --json call, the step outputs (cid, ipns, mode, contenthash, url) and the job summary, so generated YAML cannot drift from the CLI behind it. For an ipfs-mode site the summary prints the ipfs://<cid> to point ENS at, because each build has its own address.
    • NEW pinnace deploy --json: one machine-readable object (cid, mode, ipns, and the per-node ok/failed breakdown) instead of the human lines. A partial deploy is data, not just a stderr line, and the exit code is unchanged.
    • install-ci DOES NOT OWN YOUR BUILD. --emit steps renders the deploy step alone, to paste into a workflow that already builds however that repo builds. The default whole-workflow target takes --package-manager npm|pnpm|yarn (driving install + cache, with pnpm/action-setup before setup-node) and --build-command, instead of a hardcoded npm ci. The output directory stays stated via --output-dir, never auto-detected.
    • install-ci now installs when asked: --write writes the workflow and refuses to overwrite an existing one without --force. It still prints by default.

    BREAKING for anyone who generated a workflow with the old flags: install-ci now requires --site <id>, and --build-command is optional (and refused with --emit steps). Since the old output could not deploy, no working pipeline can regress.

    The emitter is now tested against its real counterparties rather than a golden string: the emitted YAML is parsed, every action input it passes is checked against the composite action's declared inputs, the token variables are checked against config-resolution's own naming rule, and an acceptance test feeds the emitted argv to the real CLI dispatch against mock Kubo nodes with only the secrets the report asks for.

Patch Changes

  • 4a77748: Document pin in the README: mirroring an external CID, and the --from-ipns migration that had no docs at all.

    pinnace pin is half the product (it is what makes the boxes a pinning service for content you did not build, not only a deploy target), and --from-ipns is the one-command migration of an existing IPNS name onto your own nodes. Neither had a walkthrough. --from-ipns was not even in the command table's pin row: its only appearance in the whole README was incidental, inside an error-message example illustrating flag strictness, so an operator holding a CID or an old publisher's name could not find out from the docs that the verb they needed exists.

    • A new "Mirror content you did not build: pin" section in the package README: mirroring a CID across every node, --host / --no-recursive, --set-mode ipns for your own stable name over someone else's content, and site remove to stop.
    • A "Migrating from an existing IPNS name" sub-section: --from-ipns with the ENS-migration example, the accepted source forms (k51..., /ipns/<id>, ipns://<id>, DNSLink), the exactly-one-source rule, and the two deliberate non-features (you get YOUR name, not the source's key; a snapshot, not a follow).
    • A "What can go wrong" sub-section: retrievability, the blocking pin/add, the pin/place/publish stage tags, some-nodes-pinned being a success, the up-front ipns-mode refusals, and pin versus site add.
    • The command table's pin row now carries the --from-ipns source form, and the root README's pitch names the mirroring capability instead of reading as deploy-only.

    Documentation only: no behaviour, flags or output changed.

pinnace@0.13.1

Choose a tag to compare

@github-actions github-actions released this 30 Jul 11:53
755a447

Patch Changes

  • 9938995: Fix the IPNS record export: routing/get was returning Kubo's JSON envelope, not the record, which broke replica re-announce.

    Kubo's HTTP RPC does not return raw record bytes for routing/get. It returns a JSON QueryEvent with the record base64-encoded in Extra; the raw bytes a ipfs routing get > file redirect produces come only from the CLI's text encoder. KuboRpcClient.routingGet was reading the body as bytes, so it handed back the envelope.

    That envelope is a 200-OK, non-empty, text body, so nothing upstream failed on it. The publisher wrote it to /records/<id>.ipns-record, replicas fetched it and routing/put it, and routing/put validates IPNS records, so the replica re-announce (the grace window the whole publisher/replica model exists for) could not work. It surfaced only once status started parsing records for the sequence number, as record is malformed / proto: cannot parse invalid wire-format data.

    • routingGet now parses the envelope and base64-decodes Extra, and REFUSES loudly (naming the node) when the body is not JSON or carries no Extra, rather than returning an empty record that would later read as "this name has no record".
    • The signed record is now BYTES end to end. PublisherFetch returns Uint8Array, the production fetch uses arrayBuffer(), and the replica cache is read and written as binary. A record is arbitrary protobuf, so any string round trip applies UTF-8 and corrupts it. Only the .ipns-name sidecar stays text.
    • The test mock gained routingGetBody(), so the real wire shape is written down once. The old tests seeded routing/get with raw bytes, encoding a contract Kubo does not honour, which is why this reached production.

    Exported records and cached records on existing boxes are rewritten in the correct form on the next republish/mirror timer tick.

    Also makes the dashboard readable: long CIDs and IPNS ids are middle-elided for display (bafybeigbx…mdakey) with the full value in the link's href and title, long unknown reasons are truncated with the full text in a tooltip, and word-break: break-all is gone. At eleven columns a 59-character CIDv1 was being shredded into a one-character-per-line ribbon.

pinnace@0.13.0

Choose a tag to compare

@github-actions github-actions released this 30 Jul 07:57
93c71c8

Minor Changes

  • cd72f21: Surface the IPNS record SEQUENCE, the number that decides which record wins, and document the failover procedure it belongs to.

    Among unexpired IPNS records the highest sequence wins, and pinnace reported it nowhere. That mattered most in the one situation the publisher/replica model exists for: when a NEW box starts signing a name that is already live, Kubo looks for the existing record locally and then in the routing system, and if both miss (likely on a box that booted minutes ago) it cannot tell that apart from a genuinely new name and silently starts at sequence 0. That record loses to the dead publisher's for the rest of its ~72h validity while every indicator reads green.

    • status now reports seq per site, on the CLI line, in status.json and as a dashboard column. It is three-valued: the number when read, unknown (<reason>) when the record could not be read, and absent when the node holds no key for the site. It is never a fallback 0, because a spurious 0 is precisely the failure it exists to expose. Comparing seq across hosts is how a half-completed failover, or two boxes signing one name, becomes visible.
    • KuboRpcClient.namePublish accepts an explicit sequence, the corrective lever (Kubo refuses anything not strictly higher than the current record, so it cannot silently regress a name). Deliberately not exposed as a CLI flag.
    • New KuboRpcClient.nameInspect decodes a raw signed record via Kubo's name/inspect; pinnace decodes no protobuf itself.
    • New failover runbook at docs/failover.md, and the README no longer implies that recovering a name means reprovisioning boxes: a replica's PUBLISHER_ENDPOINT is a URL against the publisher's dashboard vhost, so repointing one DNS record moves every replica to a new publisher with no SSH and nothing to reprovision.

pinnace@0.9.0

Choose a tag to compare

@github-actions github-actions released this 27 Jul 08:26
382e869

Minor Changes

  • f30ee4c: Release as a MINOR: an unknown flag is now a REFUSAL, which changes behaviour for any script passing a stale flag.

    • Unknown flag names are rejected. Previously parseArgs accepted any --token and stored it, so a flag no verb reads was silently ignored. Running pin ... --mode ipns after the --set-mode rename parsed cleanly, published no IPNS record, and stored mode: ipfs, which would have made republish skip the name until it lapsed. Each verb now declares its flags, an unknown one is a loud error listing the accepted set, and renamed flags get a hint (--mode suggests --set-mode).
    • eth.limo warming is visible and honest. status and the dashboard now PROBE the resolved https://<name>.limo/ and report whether it serves, instead of only naming it; and warm no longer reports warmed for a site whose every fetch failed.
    • An inferred ensName no longer displays as none/unset. A .eth site with no stored name showed "none" beside a working eth.limo link; it now shows the inferred name marked as inferred, in both the CLI and the dashboard.

    Also pins the cloud-init agent version to 0.9.0, the version this release publishes.

Patch Changes

  • 94d6aab: Make eth.limo warming VISIBLE in status and HONEST in warm: an operator can now see whether https://<name>.limo/ — the URL a human actually visits — works.

    status PROBES the eth.limo URL each site resolves, not just the CID gateway. Every site whose ensName/.eth id resolves a name (the same resolveEnsNameToWarm rule the on-box warm loop uses) gets https://<name>.limo/ probed, and the result appears in all three places the other per-site fields already do: the CLI line (eth.limo alice.eth.limo ethLimoServes=true), the status.json payload (ethLimoServes + ethLimoHttp), and the dashboard's eth.limo column, which now shows the name AND an ok/no verdict instead of only a name. A site that resolves NO name (an ensName: "" opt-out, or a non-.eth id with no explicit name) has nothing to probe and reads as NOT APPLICABLE — absent in the payload, n/a on the CLI line, no verdict on the dashboard — never as a failure. The announced and gateway columns are unchanged, as is the ok/unverified token, which stays about the CID. A probe failure is reported, never thrown: status keeps working when eth.limo is down.

    warm stops claiming success it does not have. pinnace node warm reported warmed for every site regardless of what happened — a site whose every fetch failed still reported warmed. It now records what actually occurred, per site: warmed (every attempt succeeded), partly-warmed (some did not — typically the CID gateways are hot but <name>.limo is not, the interesting case for a .eth site), warm-failed (all failed), or nothing-to-warm (nothing was attempted: no gateways configured and no ENS name). ethLimoWarmed on the per-site outcome says which half failed. A cold gateway that ANSWERS (504/404) counts as a failed warm, exactly like one that throws. The load-bearing error policy is unchanged: warming failures are RECORDED, never raised, so a cold or broken gateway still never fails the verb.

    Library note: the injectable GatewayProbe was WIDENED from (cid) => Promise<number> to (url) => Promise<number> so one probe seam covers both probed URLs rather than forking a second one; the CID's gateway URL is now built by the status core (cidGatewayUrl), and <name>.limo has a single spelling (ethLimoUrl, beside the rule that resolves the name). Anyone injecting a custom gatewayProbe receives a full URL instead of a bare cid.

  • c4819d8: Refuse an unknown CLI flag instead of ignoring it: the rule "a flag you type must never mean nothing" now covers a flag's NAME, not just its value.

    BEHAVIOUR CHANGE. Every verb declares the flags it accepts, and anything else is a loud error naming the offending flag, listing what that verb does accept, and exiting non-zero WITHOUT performing the operation. Anyone whose scripts pass a stale or misspelled flag will now see a refusal where the flag was previously parsed and silently dropped. That silence was a real, shipped failure: after the --mode -> --set-mode rename, pinnace pin --from-ipns <src> --as ronan.eth --mode ipns parsed, was read by nobody, pinned the site as ipfs with no IPNS record published, and stored mode: ipfs, which would have made the on-box republish skip the name until it lapsed. It was caught only because a human noticed a missing ipns:// line; a CI or cron run would have failed silently. The same now catches plain typos (--set-mod ipns).

    --mode gets an explicit RENAME HINT: on deploy and pin it refuses with "--mode was RENAMED: did you mean --set-mode?". The hint comes from a small static map of this project's renamed flags, and is only offered when the replacement is a flag that verb actually accepts.

    Nothing valid is refused: the accepted set was enumerated per verb from the code (provision, deploy, pin, status, derive, install-ci, site, authorize, node, version), the PREFIX-shaped --host-endpoint.<name> / --host-token.<name> stay accepted on every node-touching verb (matched by prefix, for any host name), and the globals --config / --endpoint are stripped before the check so they are never reported as unknown from either side of the command. authorize keeps its own tailored --host refusal. Three verb surfaces tighten as a consequence: deploy --host (a deploy fans out to every node by design), and any flag on node <verb> or version, are now refusals rather than silent no-ops.

    The bare-flag refusal is unchanged and composes with this one: the name check runs first, so a bare UNKNOWN flag now reports "unknown flag" rather than "needs a value", while a bare KNOWN flag still says "needs a value". parseArgs itself is untouched and no arg-parsing dependency was added.

  • b9fe50f: Stop telling an operator a site has NO ENS name when it demonstrably has an inferred one.

    Both status renderers described the STORED ensName field, but the column is READ as "what is this site's ENS name?". For a .eth site that stores none, those two answers differ, so the row contradicted itself: the CLI printed ronan.eth: ... ensName unset eth.limo ronan.eth.limo, and the dashboard showed a grey none in the ens name cell beside a ticked ronan.eth.limo link in the next one.

    The ens name column now shows FOUR states instead of three, using the ensNameToWarm the report had ALREADY resolved: a STORED name prints as itself; an ABSENT name that resolved anyway prints as the name it warms, MARKED inferred (ronan.eth (inferred) on the CLI line, ronan.eth plus a muted (inferred) hint in the dashboard cell) so it stays distinguishable from a stored name that OVERRIDES the id; "" still reads as opted-out / opted out; and a site with nothing stored and nothing resolved still reads as unset / none.

    Display only. Nothing about resolution, warming or storage moves: the three-valued stored field, resolveEnsNameToWarm and what the box warms are unchanged, and the status.json payload keeps carrying raw, unannotated ensName/ensNameToWarm (the annotation is presentation, so machine readers see exactly what they saw before). The dashboard renderer still resolves nothing itself — it renders the name the report resolved, and a .eth id the report did not resolve still reads as none.

pinnace@0.12.0

Choose a tag to compare

@github-actions github-actions released this 27 Jul 10:14
5f7d764

Minor Changes

  • f75279a: status can now say "your ENS name is not pointing at this site" and "eth.limo is serving an older CID".

    Until now the eth.limo column answered one question: does <name>.limo respond? A live box answered YES, with every other indicator green, while eth.limo was resolving through a DIFFERENT publisher's name — the operator had pinned the content with pin --from-ipns and never repointed the ENS contenthash, so pinnace was refreshing an ORPHANED name nothing referenced and the site was one old-publisher outage away from going dark.

    Each site now reports TWO INDEPENDENT axes, read from the x-ipfs-path / x-ipfs-roots headers of the probe that already ran (no second request, no second seam), in the CLI status line, the status.json payload and the dashboard:

    • ethLimoOrigin: ours (the path names this site's IPNS id, or its CID for an ipfs-mode site), foreign (<path>) NAMING the other name/cid it points at instead, frozen (<path>) (an ipns-mode site whose ENS holds an immutable /ipfs/<cid>: correct today, but it will never follow a future deploy), unknown (<reason>), or n/a for a site that resolves no ENS name.
    • ethLimoFreshness: current, stale (<served cid>), unknown (<reason>), or n/a.

    They are never collapsed into one verdict — the regression above is precisely a foreign origin serving a current cid. stale and frozen render as neutral ATTENTION states, never the red negative: a gateway lagging a fresh deploy is normal IPNS propagation, not a fault. A missing header or a probe that could not be made reports unknown WITH its reason on both axes, never a confident negative, and a site with no ENS name reports n/a (nothing to ask), which stays distinct from unknown (could not ask).

    HONESTY, stated in the code docs, the README, the dashboard footer and the glossary: these axes observe what eth.limo RESOLVED AND SERVED through its own cache. They are NOT a read of the ENS record (pinnace speaks no Ethereum RPC — wiring a name into ENS is the consumer's job), so they can lag reality and cannot tell a wrong contenthash from a stale gateway cache.

    BREAKING for library consumers who inject their own gateway probe: GatewayProbe now resolves to {status, headers?} instead of a bare status number, so the ONE probe seam carries the headers these axes need rather than a second probe type being introduced. defaultGatewayProbe and every CID-gateway behaviour are otherwise unchanged.

    Decisions

    Full rationale (alternatives considered, what each touches): work/notes/observations/ethlimo-origin-and-freshness-decisions.md.

    • The probe seam was WIDENED once to a result object rather than forked into a header-reading probe beside it, and rather than returning a number | object union that would leave one seam with two shapes. Touches every injector of GatewayProbe.
    • This is a minor, not a patch, answering the open review question about the previous widening of this same exported type: a change to an exported type's shape is a minor, even pre-1.0.
    • frozen applies to ANY /ipfs/<cid> under a name-publishing site, whether or not that cid is current: origin judges what the record POINTS AT, and freshness answers separately whether it is ours. Folding the cid comparison into the origin axis is the collapsing the two axes exist to avoid.
    • foreign is the only new state painted as a negative. For an ipfs-mode site it may be an older cid of your own; from outside there is no way to tell that from another site's cid, and the docs say so.
    • Cids are compared as strings, so a different encoding of the same cid reads as stale — one more reason stale is an attention state, not an accusation.
    • The ok/unverified roll-up token is unchanged: a mismatch does not flip it, so a consumer alerting on it must read the two new fields.

pinnace@0.11.0

Choose a tag to compare

@github-actions github-actions released this 27 Jul 09:24
5119b5f

Minor Changes

  • 2425cd7: Give the package ONE version source of truth, and make the cloud-init agent pin derive from it.

    pinnace version now prints the package's real version, BARE (0.10.0), so $(pinnace version) is directly usable in a script. It used to print the package NAME (pinnace), unfinished scaffolding, so anyone parsing the old output sees a change (both READMEs and the emitted pinnace-setup.sh use it only as a post-install smoke test, which is unaffected).

    The cloud-init agent pin (PINNACE_VERSION in /etc/pinnace-node.env, i.e. what a box runs npm install -g pinnace@<...> on) is no longer a hand-typed literal: it defaults to the version of the CLI that GENERATED the cloud-init. A box therefore installs the agent matching its generator by construction. The old literal had to be predicted and edited before merging each Version PR, and drifted when that was missed: 0.10.0 shipped correctly, but 0.8.0 shipped pinning 0.7.0, so a box provisioned from it installed the pre-correction agent. The pin's contract is unchanged in substance: still an EXACT version (never floating latest), and a given build always emits the same pin; --pinnace-version / ProvisionInput.pinnaceVersion still overrides per box.

    Both consumers read one module, PINNACE_VERSION (exported from the package root), which resolves the version from the package's own package.json at runtime, relative to its own module URL, which is correct in the dev/test path and in the built dist a global npm install -g pinnace runs on a box (proven by building and running the real dist bin in the tests, and by installing a packed tarball into a throwaway global prefix). A missing or version-less package.json fails loudly rather than reporting a placeholder version.

    Decisions

    Full rationale (alternatives considered, what each touches): work/notes/observations/derive-version-from-package-decisions.md; the observation it closes is work/notes/observations/cloud-init-version-pin-trails-the-release.md.

    • version prints BARE, not pinnace <version>. The old output was the package name, so there was no contract to preserve; bare avoids forcing every consumer to cut a field. Touches: the two READMEs' smoke test and the emitted pinnace-setup.sh (neither breaks).
    • DERIVE the pin (option 1 of the observation) rather than enforce the literal in CI (option 2). Removes the failure class instead of policing it, at the cost that the agent version is chosen by the build rather than per release by a human. DEFAULT_KUBO_VERSION stays a literal on purpose: Kubo is an external dependency a human chooses; the agent is our own code. Touches: --pinnace-version (now the only way to pin a different agent) and the release ritual (the "bump the pin" step is gone).
    • Provision tests inject a fixed FAKE pin (9.9.9); the default is asserted by comparison against PINNACE_VERSION, never a literal. Otherwise every changesets bump would redden the snapshot on main, trading a once-per-release manual edit for a once-per-release build break. Verified by running the suite green with package.json temporarily set to 99.1.2.