Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Changelog

## Unreleased
## 0.1.0-beta.1 (candidate, not published)

- Define the beta API and unchanged wire-format contract, migration checks and dependency approval boundaries.
- Read all 23 frozen existing-ID/legacy vectors through the installed archive, including exact re-encoding, canonical results and namespace isolation.
- Set the candidate publication tag to `beta`; do not move the existing `alpha` or `latest` tags. Runtime JavaScript and measured hashes remain unchanged; historical evidence is carried forward without new timings or human-performance claims.


- Document adoption decisions for existing ObjectId/search workflows, additional
alias and mapping costs, alpha versus wire compatibility, stable-release gates
Expand Down
8 changes: 4 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,15 @@ For the existing-ID format or comparison, run `pnpm compare` after the build and

The build keeps JSDoc in declarations and removes comments only from emitted JS. Keep the full-root and codec-only size figures separate; include compression costs. After runtime changes, refresh the historical comparison with `pnpm compare:previous PATH_TO_PREVIOUS_DIST FULL_COMMIT_SHA` as described in [comparison methods](docs/comparison.md#before-and-after-optimization). The recorded previous baseline is `daaffe813fd0351752bd158314917267acddc1b6`. Do not compare historical timings collected on different machines as a before/after speedup.

## Publishing an alpha
## Publishing a prerelease

Use a clean, reviewed commit and verify that its CI passed. Keep the version, changelog and evidence version in sync. A published npm name/version cannot be reused. For a prerelease, keep both `publishConfig.tag` and the explicit publication tag set to `alpha`.
Use a clean, reviewed commit and verify that its CI passed. Keep the version, changelog and evidence version in sync. A published npm name/version cannot be reused. For the beta candidate, keep both `publishConfig.tag` and the explicit publication tag set to `beta`. Do not move the existing `alpha` or `latest` tags.

1. Run `pnpm check`, `pnpm test:demo`, `pnpm test:comparison`, `pnpm test:workflows` and `pnpm test:package`; the matching CI must also cover browsers, PDF extraction/lookup and the supported Node/OS matrix.
2. Pack with `pnpm pack --pack-destination artifacts`. Record the commit, package SHA-256 and npm SHA-512 integrity. Inspect the file inventory and install this exact archive into an isolated consumer before publication.
3. Authenticate to `https://registry.npmjs.org` with `npm login --auth-type=web`. Enter passwords and second-factor challenges only in npm's login flow. Never commit credentials or put them in an issue/PR. Direct publication requires account 2FA or another authentication method supported by npm; prefer the interactive account flow for the first release.
4. Run `npm publish ./artifacts/refmint-VERSION.tgz --dry-run --ignore-scripts --tag alpha --access public --registry=https://registry.npmjs.org`, substituting the actual version. Inspect the name, version, files and integrity. Then publish the same archive by removing only `--dry-run`.
5. Read the public registry metadata back. Check the version, `alpha` tag and tarball integrity, then install `refmint@alpha` in a fresh consumer and exercise both `refmint` and `refmint/codec`. Download the registry tarball and compare it with the published archive. Keep the proof with the release record.
4. Run `npm publish ./artifacts/refmint-VERSION.tgz --dry-run --ignore-scripts --tag beta --access public --registry=https://registry.npmjs.org`, substituting the actual version. Inspect the name, version, files and integrity. Then publish the same archive by removing only `--dry-run`.
5. Read the public registry metadata back. Check the version, `beta` tag and tarball integrity, then install `refmint@beta` in a fresh consumer and exercise both `refmint` and `refmint/codec`. Download the registry tarball and compare it with the published archive. Keep the proof with the release record.

The first npm alpha.2 archive has updated README, changelog and contributor documentation. All other files match the original GitHub alpha.2 archive; the original tag, download and checksums remain intact. Future releases should prepare registry instructions before creating either archive.

Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# RefMint

**Beta candidate:** this checkout prepares `0.1.0-beta.1`; it is not published.
Installation links below still point to the published alpha.3. Read the
[beta API/format contract](docs/beta-contract.md) before upgrading.

**Reference codes you can generate, print and check.**

[![CI](https://github.com/PerchAnton/refmint/actions/workflows/ci.yml/badge.svg)](https://github.com/PerchAnton/refmint/actions/workflows/ci.yml)
Expand All @@ -14,7 +18,7 @@ RefMint is a small TypeScript library for public ticket, document and order refe

**Retained from alpha.2:** preserve an existing uint64 or UUID with `createReferenceCodec`, inspect invalid input with `diagnose`, and load only the codec with `refmint/codec`. The release includes the optimized implementation and comparison evidence; the tagged source includes the complete comparison form. [Format v1](docs/format-v1.md) · [Comparison and measured trade-offs](docs/comparison.md).

**Alpha:** APIs may evolve. The existing-ID v1 wire contract is frozen; incompatible formats require a new version. Install the [npm alpha](https://www.npmjs.com/package/refmint), [download the GitHub prerelease](https://github.com/PerchAnton/refmint/releases/tag/v0.1.0-alpha.3), or build from source. The package uses ES modules and includes TypeScript declarations. Node.js 22+ and browsers with Web Crypto are the targets.
**Published alpha:** APIs may evolve. The existing-ID v1 wire contract is frozen; incompatible formats require a new version. Install the [npm alpha](https://www.npmjs.com/package/refmint), [download the GitHub prerelease](https://github.com/PerchAnton/refmint/releases/tag/v0.1.0-alpha.3), or build from source. The package uses ES modules and includes TypeScript declarations. Node.js 22+ and browsers with Web Crypto are the targets.

![Measured checks and the reference lifecycle](docs/assets/evidence.svg)

Expand Down
57 changes: 57 additions & 0 deletions docs/beta-contract.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# Beta API and format contract

This checkout prepares **0.1.0-beta.1**, not yet published. The registry's current
alpha is **0.1.0-alpha.3**. Beta adds an explicit compatibility boundary and checks
the frozen identifiers through the installed archive. Runtime JavaScript is
unchanged: there is no new speed, size, usability or adoption claim.

## What stays compatible

| Surface | Commitment for beta |
| --- | --- |
| Imports | ESM `refmint` and `refmint/codec`; the same exported factories/functions and TypeScript types; no runtime dependencies |
| Platforms | Node 22+; browsers with the standard APIs used by the package; secure Web Crypto is required for random generation |
| Random references | Existing prefix/length configuration and canonical identifiers remain readable; keep configuration with the stored schema |
| Existing-ID codec | Frozen format v1 for uint64/UUID, same canonical bytes and normalization; incompatible encodings require a different wire version |
| Values | uint64 uses `bigint`, including above the safe-number limit; UUID input uses canonical lowercase hyphenated text; no ObjectId encoding or truncation |
| Results | Keep the `ok` discriminant, canonical `id`, `normalized`, codec `value`, and current error codes; human input methods return failures rather than correcting an identifier |
| Diagnostics | Preserve raw-input position semantics and bounded details. Treat human prose and the ordering of optional diagnostic details as non-contractual |
| Rejection | Preserve finite input bounds, Unicode-lookalike rejection and strict canonical methods. Invalid options/encoding values still throw; parse/decode do not coerce arbitrary input objects |

The random API and existing-ID codec remain different namespaces; neither accepts
the other's format. Checksums detect many entry errors, not a valid-but-wrong record,
and do not provide authorization, secrecy, uniqueness or authentication. A random
reference still needs a unique database index and collision retry. No automatic
database migration, search endpoint or regulated invoice-number replacement occurs.

## Upgrade checks

Once published, pin `refmint@0.1.0-beta.1`; publish under **beta**, keeping `alpha`
and `latest` unchanged. Existing alpha.2/alpha.3 callers and identifiers need no
API or data migration. Keep the prior reader during application rollout and rerun
the application's native lookup/permission tests on the exact installed archive.

`pnpm test:package` reads all 20 existing-ID and 3 legacy frozen vectors through
the installed package, verifies exact re-encoding/canonical results, and rejects
cross-namespace use. It also checks public type branding, private import blocking,
zero dependencies, shipped documentation and unchanged measured runtime hashes.
The fixtures are not regenerated for beta. Existing independent-oracle, input,
workflow, PDF and three-engine browser checks remain required.

Preserve this contract during beta. A necessary incompatible correction requires a
new prerelease, explicit migration notes and regression evidence; never replace
an existing version. Internal modules, example UI and benchmark timings are outside
this contract. A stable 1.0 support/deprecation policy is a separate decision.

## What beta does not prove

The [existing workflow comparison](workflows.md) is synthetic. The
[human study](manual-entry-study.md) still has no results establishing shorter lookup
times or fewer mistakes. An existing number, UUID search or short alias may remain
better; [adoption costs](adoption.md) do not disappear with a version label.

We can supply exact archive/source hashes, licenses, dependency/type inventory,
compatibility tests and a scoped integration/local-alias comparison. Maintainers
decide whether the workflow needs another code, which source to accept, and who
owns storage, permission checks, deployment and updates. Do not weaken their gates
or label the package approved because a pin or beta package exists.
2 changes: 1 addition & 1 deletion docs/evidence.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"generated_at": "2026-10-27T13:21:13.955Z",
"version": "0.1.0-alpha.3",
"version": "0.1.0-beta.1",
"environment": {
"node": "v24.19.0",
"platform": "darwin",
Expand Down
2 changes: 1 addition & 1 deletion docs/evidence.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Run `pnpm check && pnpm evidence` from a clean checkout after installing the loc

## Correctness

For alpha.3, the timing/size snapshot is carried forward from alpha.2 because all
For alpha.3 and the beta.1 candidate, the timing/size snapshot is carried forward from alpha.2 because all
runtime JavaScript hashes are identical. `measurement_version` identifies the measured
version; `version` identifies the package carrying the evidence. Original timestamps,
environment and samples are retained. This is not a new performance measurement.
Expand Down
4 changes: 4 additions & 0 deletions docs/stability.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Alpha scope and stability criteria

The checkout now prepares a [beta.1 compatibility contract](beta-contract.md).
It is not yet published. The alpha history and stable-release gates below still
apply; beta does not supply human-benefit evidence or promise stable 1.0 support.

RefMint 0.1.0-alpha.3 is a prerelease. npm publication and successful CI do not
change that status. Pin the exact reviewed version for an evaluation and keep the
existing identifier/search path available. There is no stable-release date promised.
Expand Down
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "refmint",
"version": "0.1.0-alpha.3",
"version": "0.1.0-beta.1",
"description": "Generate public references or encode existing IDs with strict input diagnostics and a Bech32m checksum.",
"type": "module",
"license": "MIT OR Apache-2.0",
Expand All @@ -22,7 +22,10 @@
"node": ">=22"
},
"sideEffects": false,
"publishConfig": { "access": "public", "tag": "alpha" },
"publishConfig": {
"access": "public",
"tag": "beta"
},
"exports": {
".": {
"types": "./dist/index.d.ts",
Expand Down
23 changes: 22 additions & 1 deletion scripts/check-package.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ try {
const installed = join(consumer, 'node_modules', 'refmint');
const packaged = JSON.parse(await readFile(join(installed, 'package.json'), 'utf8'));
assert.equal(packaged.version, source.version);
assert.equal(packaged.publishConfig.tag, 'alpha');
assert.equal(packaged.publishConfig.tag, 'beta');
assert.deepEqual(packaged.dependencies ?? {}, {});
const files = await readdir(installed, { recursive: true });
assert.deepEqual((await readdir(join(installed, 'dist'))).sort(),
Expand All @@ -66,8 +66,10 @@ try {
assert.ok((await readFile(path)).length > 0, `${file} -> ${target}`);
}
}
await writeFile(join(consumer, 'formats-v1.json'), await readFile(join(root, 'tests/fixtures/formats-v1.json')));
await writeFile(join(consumer, 'smoke.mjs'), `
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import { createReferences, createReferenceCodec, planReferences, collisionProbability } from 'refmint';
import { createReferenceCodec as standalone } from 'refmint/codec';
assert.equal(standalone, createReferenceCodec);
Expand All @@ -83,6 +85,25 @@ const encoded = codec.encode(9007199254740993n);
assert.equal(codec.decode(codec.format(encoded).toUpperCase()).value, 9007199254740993n);
assert.equal(codec.diagnose(encoded.replace('r', '\\uff52')).details.positions[0], 0);
assert.equal(refs.parse(encoded).ok, false);
// Read the old vectors with the installed archive, not the checkout's build.
const frozen = JSON.parse(await readFile(new URL('./formats-v1.json', import.meta.url), 'utf8'));
for (const fixture of frozen.codec) {
const reader = createReferenceCodec({prefix:fixture.prefix,kind:fixture.kind,version:1});
const value = fixture.kind === 'uint64' ? BigInt(fixture.value) : fixture.value;
assert.equal(reader.encode(value), fixture.reference);
assert.deepEqual(reader.decodeCanonical(fixture.reference),
{ok:true,id:fixture.reference,value,normalized:false});
assert.deepEqual(reader.decode(reader.format(fixture.reference).toUpperCase()),
{ok:true,id:fixture.reference,value,normalized:true});
assert.equal(createReferences({prefix:fixture.prefix}).parse(fixture.reference).ok, false);
}
for (const fixture of frozen.legacy) {
const reader = createReferences({prefix:fixture.prefix,randomLength:fixture.randomLength});
assert.deepEqual(reader.parseCanonical(fixture.reference),
{ok:true,id:fixture.reference,normalized:false});
assert.equal(reader.parse(reader.format(fixture.reference).toUpperCase()).id, fixture.reference);
assert.equal(createReferenceCodec({prefix:fixture.prefix,kind:'uint64'}).decode(fixture.reference).ok, false);
}
await assert.rejects(import('refmint/dist/bech32m.js'), {code:'ERR_PACKAGE_PATH_NOT_EXPORTED'});
`);
run(join(consumer, 'smoke.mjs'), [], consumer);
Expand Down
Loading