diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 7622f41..43dd646 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -4,4 +4,4 @@ Source: the repository and pinned tag or commit with the file and line ranges re Tier: source-read, live-chain, or both. -Row in `validation-log.md` updated: yes, or why the change needs none. +Row in `reference/validation.md` updated: yes, or why the change needs none. diff --git a/.github/scripts/check-frontmatter.mjs b/.github/scripts/check-frontmatter.mjs index 164b1ce..d785d28 100644 --- a/.github/scripts/check-frontmatter.mjs +++ b/.github/scripts/check-frontmatter.mjs @@ -16,7 +16,7 @@ import { leadingHeading, pagesUnder, readPage, FrontmatterError } from './lib/pa /** Every tree the schema binds. `skills/` carries a skill's own frontmatter and README.md carries none. */ const TREES = ['reference', 'guides', 'tutorials', 'concepts', 'learning']; -const ROOT_PAGES = ['AGENTS.md', 'CLAUDE.md', 'validation-log.md']; +const ROOT_PAGES = ['AGENTS.md', 'CLAUDE.md']; /** * The trees the docs site renders as routes. The band below is a meta diff --git a/.github/scripts/check-validation-consistency.mjs b/.github/scripts/check-validation-consistency.mjs index d4b4fde..7276e7a 100644 --- a/.github/scripts/check-validation-consistency.mjs +++ b/.github/scripts/check-validation-consistency.mjs @@ -14,7 +14,7 @@ */ import { readFile } from 'node:fs/promises'; import { join, resolve } from 'node:path'; -import { pagesUnder, readPage } from './lib/pages.mjs'; +import { pagesUnder, readPage, section } from './lib/pages.mjs'; /** The trees the ledger grades. Tutorials and concepts carry no tier by design. */ const GRADED = ['reference', 'guides']; @@ -22,26 +22,16 @@ const GRADED = ['reference', 'guides']; /** Every tree that carries `key-modules`, so a new one is covered when it lands. */ const PINNED = ['reference', 'guides', 'tutorials', 'concepts']; -/** The ledger's own path. U8 moves it into the rendered tree; both spellings resolve. */ -const LEDGER = ['validation-log.md', 'reference/validation.md']; +/** The ledger's own path. It sits in a graded tree and takes no row of its own. */ +const LEDGER = 'reference/validation.md'; const root = resolve(process.argv[2] ?? process.cwd()); -/** The body of one `## ` section, by its exact heading text. */ -function section(source, heading) { - const pattern = new RegExp(String.raw`^## ${heading}\s*$([\s\S]*?)(?=^## |\Z)`, 'm'); - const found = pattern.exec(source); - - return found === null ? null : found[1]; -} - async function readLedger() { - for (const path of LEDGER) { - try { - return { path, source: await readFile(join(root, path), 'utf8') }; - } catch (error) { - if (error.code !== 'ENOENT') throw error; - } + try { + return { path: LEDGER, source: await readFile(join(root, LEDGER), 'utf8') }; + } catch (error) { + if (error.code !== 'ENOENT') throw error; } return null; @@ -51,7 +41,7 @@ const ledger = await readLedger(); const findings = []; if (ledger === null) { - console.error(`error: no provenance ledger at ${LEDGER.join(' or ')}`); + console.error(`error: no provenance ledger at ${LEDGER}`); process.exit(1); } diff --git a/.github/scripts/compose-release-notes.mjs b/.github/scripts/compose-release-notes.mjs new file mode 100644 index 0000000..7df4711 --- /dev/null +++ b/.github/scripts/compose-release-notes.mjs @@ -0,0 +1,64 @@ +#!/usr/bin/env node +/** + * Composes the body of a GitHub Release from the ledger's pinned baselines. + * + * A `YYYY.MM.PATCH` tag says when the corpus moved and nothing else, because a + * knowledge corpus has no API surface to break. What a reader can act on is the + * set of releases the pages were read against: a fact here is a fact about one + * pinned contract, indexer, or library version. The ledger is where those pins + * live, so the Release copies them rather than keeping a second list that drifts. + * + * Usage: node .github/scripts/compose-release-notes.mjs [root] + */ +import { readFile } from 'node:fs/promises'; +import { join, resolve } from 'node:path'; +import { section } from './lib/pages.mjs'; + +const LEDGER = 'reference/validation.md'; + +const [tag, root = process.cwd()] = process.argv.slice(2); + +if (tag === undefined) { + console.error('usage: node .github/scripts/compose-release-notes.mjs [root]'); + process.exit(2); +} + +let source; +try { + source = await readFile(join(resolve(root), LEDGER), 'utf8'); +} catch (error) { + console.error(`error: cannot read ${LEDGER}: ${error instanceof Error ? error.message : String(error)}`); + process.exit(1); +} +const baselines = section(source, 'Pinned baselines'); + +if (baselines === null) { + console.error(`error: ${LEDGER} has no "## Pinned baselines" section`); + process.exit(1); +} + +const pins = [...baselines.matchAll(/^- .*$/gm)].map((found) => found[0]); + +/** + * A Release body naming no baseline is the failure this script exists to + * prevent, so it fails the run rather than publishing an empty list. + */ +if (pins.length === 0) { + console.error(`error: ${LEDGER} lists no baseline under "## Pinned baselines"`); + process.exit(1); +} + +const repository = process.env.GITHUB_REPOSITORY ?? 'atomicassets/atomic-knowledge'; +const blob = `https://github.com/${repository}/blob/${tag}`; + +process.stdout.write( + [ + 'The pages at this tag are read against these baselines. Each page names the ones it draws from in its own `key-modules` line.', + '', + ...pins, + '', + `What changed: [CHANGELOG.md](${blob}/CHANGELOG.md).`, + `How each page was checked: [the provenance ledger](${blob}/${LEDGER}).`, + '', + ].join('\n'), +); diff --git a/.github/scripts/lib/pages.mjs b/.github/scripts/lib/pages.mjs index e06a789..b6dc175 100644 --- a/.github/scripts/lib/pages.mjs +++ b/.github/scripts/lib/pages.mjs @@ -1,6 +1,6 @@ /** - * Reads the frontmatter block every page in this corpus carries, and walks the - * page trees the checks run over. + * Reads the frontmatter block every page in this corpus carries, walks the page + * trees the checks run over, and cuts a named section out of a page. * * The reader covers the shapes `.github/frontmatter.schema.json` admits and * nothing else: a scalar, a flow sequence, and a block sequence, each of @@ -122,6 +122,24 @@ export function leadingHeading(body) { return null; } +/** + * The body of one `## ` section, by its exact heading text, or null when the + * page has no such heading. The section runs to the next `## ` line or to the + * end of the page, which is what lets a caller read the last section of a file + * as well as one in the middle. + */ +export function section(source, heading) { + const lines = source.split('\n'); + const start = lines.findIndex((line) => line.trimEnd() === `## ${heading}`); + + if (start === -1) return null; + + const rest = lines.slice(start + 1); + const end = rest.findIndex((line) => line.startsWith('## ')); + + return (end === -1 ? rest : rest.slice(0, end)).join('\n'); +} + /** Every markdown page under `directory`, repository-relative, sorted. */ export async function pagesUnder(root, directory, found = []) { let entries; diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..6b810b3 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,105 @@ +# Cuts a tag and a Release for what just merged. Tags are `YYYY.MM.PATCH`, one +# per merged pull request: a corpus has no API surface to break, so a semantic +# major and minor carry nothing a reader can act on, while recency and the +# baselines behind the pages are exactly what a reader wants. The Release body +# copies those baselines from the ledger. +# +# The trigger is a branch push, so a tag push never reaches this workflow and +# the tag it creates cannot start a second run. A run whose commit already +# carries a tag stops before the Release, which is what makes a re-run of an +# older commit harmless. +# +# Actions are pinned by commit sha with the version in the trailing comment, the +# same as the checks workflow. +name: release + +on: + push: + branches: [main] + +# The tag and the Release are the only writes this workflow makes. +permissions: + contents: write + +concurrency: + # Two merges landing together would read the same tag list and compute the + # same patch number, so they queue rather than race. A queued run is never + # cancelled: the merge it belongs to is already on the default branch. + group: release + cancel-in-progress: false + +jobs: + release: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + with: + # The existing tags are the input to the patch number, and a + # shallow checkout carries none of them, which would restart + # every month at zero and collide with a tag that exists. + fetch-depth: 0 + + - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0 + with: + node-version: '24' + + - name: The next tag for this month + id: next + run: | + set -euo pipefail + + if [ -n "$(git tag --points-at HEAD)" ]; then + echo "::notice::$(git rev-parse --short HEAD) already carries a tag, so this run cuts nothing" + echo "tag=" >> "${GITHUB_OUTPUT}" + exit 0 + fi + + # The merge commit's own date in UTC rather than the runner + # clock, so a run that starts either side of midnight tags the + # month the merge landed in. Git formats it, so nothing here + # needs a date library. + prefix="$(TZ=UTC git show --no-patch --date=format-local:%Y.%m --format=%cd HEAD)" + + # The first release of a month is patch zero, and each one + # after it is the highest suffix already used that month plus + # one. The comparison is numeric, so .10 follows .9 rather than + # sorting between .1 and .2, and a tag whose suffix is not a + # number is not a release of this scheme and is passed over. + patch=0 + for existing in $(git tag --list "${prefix}.*"); do + suffix="${existing##*.}" + case "${suffix}" in '' | *[!0-9]*) continue ;; esac + if [ "${suffix}" -ge "${patch}" ]; then patch=$((suffix + 1)); fi + done + + tag="${prefix}.${patch}" + + # Shape-checked before the write, because everything below + # takes this value on trust: it names the tag the API creates + # and titles the Release. + if [[ ! "${tag}" =~ ^[0-9]{4}\.[0-9]{2}\.[0-9]+$ ]]; then + echo "::error::computed tag is not YYYY.MM.PATCH: ${tag}" + exit 1 + fi + + echo "tag=${tag}" >> "${GITHUB_OUTPUT}" + echo "cutting ${tag}" + + - name: The Release, naming the baselines the corpus was read against + if: steps.next.outputs.tag != '' + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ steps.next.outputs.tag }} + run: | + set -euo pipefail + + node .github/scripts/compose-release-notes.mjs "${TAG}" > "${RUNNER_TEMP}/release-notes.md" + + # One call creates the tag and the Release together, so a + # failure cannot leave a tag standing with no Release behind + # it, and the next run reads a tag list that means what it says. + gh release create "${TAG}" \ + --target "${GITHUB_SHA}" \ + --title "${TAG}" \ + --notes-file "${RUNNER_TEMP}/release-notes.md" diff --git a/AGENTS.md b/AGENTS.md index 9454ec5..67d8845 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -62,7 +62,7 @@ Start here. Find the outcome you are working toward below, read the file on that Read the routed file in full rather than searching it. The facts there encode behavior no method signature carries: which account is billed for a row, which read answers with a null instead of an error, which flag lets a transaction commit while delivering nothing. -Take each section at face value and do not extrapolate past what it states. A page says what was checked, and what it does not say was not checked. `validation-log.md` records how every page was validated and against what. It grades `reference/` and `guides/` only: a tutorial's claim is that its steps run, and a concepts page restates facts the pages it links already carry, so neither takes a row there. +Take each section at face value and do not extrapolate past what it states. A page says what was checked, and what it does not say was not checked. `reference/validation.md` records how every page was validated and against what. It grades `reference/` and `guides/` only: a tutorial's claim is that its steps run, and a concepts page restates facts the pages it links already carry, so neither takes a row there. Re-check any fact that names a version when that dependency moves. The two SDK pages are pinned to `@atomichub/atomicassets` 2.1.1, read at tag `v2.1.1`, and `@atomichub/atomicmarket` 2.4.1, read at tag `v2.4.1`; the client-library page is pinned to `@wharfkit/antelope` 1.1.1, and the AtomicAssets and AtomicMarket contract pages to `v2.0.0-rc4` and `v2.0.0-rc2`. A fact read at one of those pins is a fact about that release, not about the package name. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..169ef1a --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,48 @@ +# Changelog + +What each release of this corpus changed, one release per tag. `Corrected` comes first in every release, because a fact that was wrong is what a returning reader has to see before anything else. The other sections are `Added`, `Revalidated`, and `Removed`, in that order, and a section with nothing in it is left out. + +## 2026.08.0 + +### Corrected + +- `reference/sdk/atomicassets.md` no longer says that constructing an `ExplorerApi` fires a `/v1/config` request: construction starts no request, and the action surface is a getter that resolves on first use. +- `reference/sdk/atomicassets.md` no longer says that `transfer` remaps its account parameters. The signature takes `from` and `to`, so there is nothing to remap. +- `reference/sdk/atomicmarket.md` no longer says the action layer covers the royalty configuration alone. It covers the sale, auction, buyoffer, template-buyoffer, marketplace, balance, royalty, and RAM families, with five composers over them. +- `reference/sdk/atomicmarket.md` and `reference/api.md` say the mainnet royalty route answers 416 rather than 404, which inverts the guard advice: `getRoyaltyConfig` maps 416 to `null`, so a caller matching on `ApiError` sees a silent `null` instead. +- `reference/api.md` names both sales list routes the hosted deployment answers on, because the served OpenAPI document describes only the newer one and a generated client disagrees with a hand-written one about which exists. +- `reference/sdk/atomicmarket.md` samples `getConfig` from a deployment that runs V2, because the mainnet value it carried belongs to a chain with no royalty layer at all. +- `reference/atomicassets/structure.md` describes an asset as the individually owned item at the bottom of the four-level model, which reserves the fungible vocabulary for the balances the contract holds. +- `reference/atomicassets/structure.md` and `reference/atomicmarket/fees-and-royalties.md` point their three dead cross-references at something a reader can reach. +- `guides/deposits.md`, `guides/links.md`, `guides/sales.md`, and `reference/atomicassets/backing-tokens.md` write `account` when the holder of an asset or a balance is an account, rather than naming the signing software in front of it. +- Thirty-one `scope` lines across `reference/` and `guides/` moved into the 140 to 160 character band the site renders as a meta description, and twenty more write the product name as a proper noun or mark the account name as code. +- `guides/asset-lifecycle.md` is titled with the query it answers rather than with its category, and three headings across `guides/links.md`, `reference/sdk/atomicassets.md`, and `reference/sdk/atomicmarket.md` write the product name the way the product spells it. +- `reference/validation.md` lists `@wharfkit/antelope` 1.1.1 among the pinned baselines, a source its own row for `reference/wharfkit.md` already named. + +### Added + +- `tutorials/first-collection.md` walks an empty WAX testnet account to a minted asset, with a checkpoint after every step and an appendix for each way a step fails. +- `tutorials/starters.md` names each starter, what it does, and whether it needs a key. +- `concepts/` explains why the protocol is shaped the way it is, one page each for the four-level model, ownership as chain state, the single order book, royalties as settlement math, choosing a read path, and the comparison against the single-token standard on an EVM chain. +- `starters/` carries five directories a reader clones and runs whole. `read-assets` and `storefront-read` need no key; `create-collection`, `mint-asset`, and `list-a-sale` sign on WAX testnet. +- `guides/signing.md` builds the session every write snippet in this corpus opens against, with one chain id per chain read from a running node. +- `guides/asset-lifecycle.md` shows the same mint built through the SDK builder beside the hand-written payload, and names the numeric guards that throw at the call instead of on chain. +- `guides/sales.md`, `guides/auctions.md`, and `guides/buyoffers.md` name the composer behind each multi-action flow, its memo literals, the two bundle opt-outs, and what a settlement quantity has to be. +- `guides/querying-the-api.md` raises percent-encoding and the testnet host pair out of its preamble into sections of their own. +- `reference/sdk/atomicassets.md` and `reference/sdk/atomicmarket.md` carry their read and action surfaces as tables, with the path-segment guard, the numeric ABI-type guards, the settlement helpers, and the royalty payout ledger the shipped releases added. +- `guides/auctions.md`, `guides/sales.md`, `reference/atomicassets/structure.md`, and `reference/atomicmarket/fees-and-royalties.md` open on a diagram of the sequence their prose states worst. +- `reference/validation.md` carries the provenance ledger, which moved out of the repository root so it renders beside the pages it grades. +- `AGENTS.md` routes by the outcome an agent arrives with, one row per outcome, and names the pins a version-sensitive fact has to be re-checked against. +- `skills/atomic-integration/SKILL.md` carries the mint procedure, the market composers, and the network choice instead of one indirection, and `skills/report/SKILL.md` opens a report with the fields the issue forms accept. +- `.github/frontmatter.schema.json` states the three frontmatter keys a page may carry, and `.github/workflows/checks.yml` gates a merge on ten checks a reviewer cannot run by eye, among them a fragment with no matching heading, a description outside the band the site renders it into, and a ledger row for a page that no longer exists. +- `reference/atomicassets/tables.md` and `reference/atomicassets/actions.md` state that no action decrements a template's `issued_supply`, `burnasset` included, so the field counts lifetime mints and a circulating supply has to subtract burns. +- `reference/atomicmarket/fees-and-royalties.md` states that no per-listing guard caps a seller's exposure to the execution-time collection fee, because `assertsale` takes no fee parameter. +- `reference/atomicassets/actions.md` and `reference/atomicmarket/ram.md` pin the two RAM byte costs a caller pays: 112 bytes for the table scope a first transfer creates, and 121 + 16N bytes for a market balances row holding N token symbols, both computed from a pinned `AntelopeIO/leap` baseline and observed on WAX mainnet. + +### Revalidated + +- `guides/asset-lifecycle.md`, `guides/auctions.md`, `guides/buyoffers.md`, and `guides/sales.md` were re-read at `@atomichub/atomicassets` 2.1.1 and `@atomichub/atomicmarket` 2.4.1. Every builder and helper they cite is byte-identical to the tag before it, so only the pins moved. + +### Removed + +- `CLAUDE.md` drops the six lines that restated `AGENTS.md` and keeps the pointer to it. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 44f1e21..7afcb7e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -12,13 +12,13 @@ If a claim has not been checked against source at a pinned tag, or against a liv ## Tiers and the ledger -`validation-log.md` grades every polished page: +`reference/validation.md` grades every polished page: - `source-read`: read from the pinned contract or library source, so the fact is a property of code that does not move without a release. - `live-chain`: confirmed by a read against a live endpoint, so the fact is a property of observed behavior. - `both`: the page carries facts of each kind, or one fact was checked both ways. -A change that adds a page, or that changes what a page claims, updates that page's row in `validation-log.md` with the tier and the baseline the page draws from. The ledger and the pages are one artifact; a page with no row is an unfinished change. +A change that adds a page, or that changes what a page claims, updates that page's row in `reference/validation.md` with the tier and the baseline the page draws from. The ledger and the pages are one artifact; a page with no row is an unfinished change. ## Pull requests diff --git a/README.md b/README.md index 33c4c12..b44390d 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Agents read `AGENTS.md`, whose routing table maps a task to the file that answer ## How facts are validated -`validation-log.md` is the provenance ledger. It records, page by page, which source was read or which endpoint was probed, and it grades each page `source-read`, `live-chain`, or `both`. A claim that has not been checked that way stays in `learning/`, the unverified tier whose promotion gate is `learning/INSTRUCTIONS.md`. +`reference/validation.md` is the provenance ledger. It records, page by page, which source was read or which endpoint was probed, and it grades each page `source-read`, `live-chain`, or `both`. A claim that has not been checked that way stays in `learning/`, the unverified tier whose promotion gate is `learning/INSTRUCTIONS.md`. The pages are read against these baselines: @@ -23,7 +23,7 @@ The pages are read against these baselines: - `atomicassets-sdk` at main `80580c5` and `atomicmarket-sdk` at main `278bdfa`, both version 2.0.0 - `@atomichub/vert` at `2.2.0` -WAX mainnet still runs the V1 `atomicassets` and `atomicmarket` contracts while WAX testnet and jungle4 run V2, so an action that exists only in V2 fails when it is sent to mainnet (`validation-log.md`). +WAX mainnet still runs the V1 `atomicassets` and `atomicmarket` contracts while WAX testnet and jungle4 run V2, so an action that exists only in V2 fails when it is sent to mainnet (`reference/validation.md`). ## What it covers diff --git a/learning/INSTRUCTIONS.md b/learning/INSTRUCTIONS.md index a396bb5..9924fb6 100644 --- a/learning/INSTRUCTIONS.md +++ b/learning/INSTRUCTIONS.md @@ -30,7 +30,7 @@ An entry with no `Promote to:` line has no destination yet and should say so rat ## Verification tiers -These tiers are used by `validation-log.md` at the repo root to record how each polished fact was checked: +These tiers are used by `reference/validation.md` to record how each polished fact was checked: - **source-read**: verified by reading the pinned contract (or library) source directly; the fact is a property of code that does not change without a new release. - **live-chain**: confirmed by an actual read against a live endpoint (a nodeos RPC call or a hosted API request); the fact is a property of observed behavior at the time of the read. diff --git a/validation-log.md b/reference/validation.md similarity index 99% rename from validation-log.md rename to reference/validation.md index 3c29d2f..28f25ef 100644 --- a/validation-log.md +++ b/reference/validation.md @@ -1,5 +1,5 @@ --- -scope: Provenance ledger - how each polished-tier page's facts were validated, and against what +scope: Provenance ledger for this corpus - how every fact in the reference and guides trees was checked, what it was read against, and the tier each page carries depends-on: [] key-modules: [] --- diff --git a/skills/atomic-integration/SKILL.md b/skills/atomic-integration/SKILL.md index 01e236a..bc80d20 100644 --- a/skills/atomic-integration/SKILL.md +++ b/skills/atomic-integration/SKILL.md @@ -84,4 +84,4 @@ Full detail: `reference/sdk/atomicmarket.md` for the composers and the settlemen ## Version pins -Re-check a fact that names a version when that dependency moves. This skill is written against `@atomichub/atomicassets` 2.1.1 and `@atomichub/atomicmarket` 2.4.1, and the contract behavior against `atomicassets-contract` v2.0.0-rc4 and `atomicmarket-contract` v2.0.0-rc2. `validation-log.md` records how each page was validated and against what. +Re-check a fact that names a version when that dependency moves. This skill is written against `@atomichub/atomicassets` 2.1.1 and `@atomichub/atomicmarket` 2.4.1, and the contract behavior against `atomicassets-contract` v2.0.0-rc4 and `atomicmarket-contract` v2.0.0-rc2. `reference/validation.md` records how each page was validated and against what.