Published as @logicsrc/openprd@0.2.0 — the first release of this package, and
the first carrying OpenPRD 0.3 (Tech Stack and Monetization, #144).
Two things had to change for the publish to be usable:
- The @logicsrc/validators dependency was `file:../validators`. npm publishes
that spec verbatim, so every install outside this monorepo would have
failed to resolve it. It is now `^0.1.0`, which is what is on the registry;
npm workspaces still links the local package for development, since 0.1.0
satisfies the range.
- Added a README. Without one the npm page reads "No README data found",
which is a poor landing surface for the reference implementation of a
public standard. @logicsrc/schemas already ships one.
Verified by installing 0.2.0 from the registry into an empty project: it
resolves @logicsrc/validators@0.1.0 and @logicsrc/schemas@0.1.0, and the
runtime reports OPENPRD_VERSION 0.3 with ten sections, eight for 0.2.
Claude-Session: https://claude.ai/code/session_017XRNNm6pK6nPi7rJ6bJNHu
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
OpenPRD 0.2 fixed eight body sections, none of which asked what the thing is
built on or how it earns. The stack got chosen in the first implementation PR
instead of at review, and a PRD could be filled out completely without anyone
writing down who pays. PRD 0006 had already grown a hand-rolled
`## Business model` section, which is the gap showing.
0.3 adds two required sections between `UX Notes` and `Success Metrics`:
- Tech Stack — languages, frameworks, datastores, third-party services, and
anything the work must not depend on. It makes the requirements costable.
- Monetization — the revenue model: who pays, for what, how much, and when.
`_None._` stays a valid answer, but it now has to be said out loud.
Adding required sections would normally invalidate every document already
written, so a document is now held to the section list its own `openprd:` key
fixes. A 0.2 document keeps conforming with eight sections, forever; a 0.3
document needs ten. Adoption is per document, and `logicsrc prd validate
--expect-version 0.3` (new flag, wiring up the validator option that already
existed) reports the stragglers as OP-L-VERSION.
The front-matter schema is untouched — both additions are body sections.
Conformance bundle proves both directions: invalid/missing-monetization.md
fails with OP-C-SECTION-MISSING, and valid/legacy-0-2.md passes unedited.
This repo's own PRDs 0001-0006 stay at 0.2 as standing evidence that the
compatibility rule holds. PRD 0007 records the decision at 0.3.
Claude-Session: https://claude.ai/code/session_017XRNNm6pK6nPi7rJ6bJNHu
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Add pay2seed to the OpenSwarm family: consent at upload and a paid seed market
OpenSwarm pays a seeder per verified piece served, and nothing pays anyone
to stay. An archive, a backup, a dataset waiting for its buyer or a
podcast's back catalogue earns nothing the month nobody downloads it, so
it dies the way every swarm always has. And nothing in BitTorrent says who
put a swarm there or whether they were allowed to, which is why a seeder
is presumed to be doing something wrong.
pay2seed is the member document for both halves. An attestation, signed
at upload with a fixed basis (own, licensed, open-license, public-domain,
personal) and a notice endpoint, is what a hub requires before it will
list anything; public claims get a claim window and a standing, and a
notice voids them. An offer escrows a budget at an ippay hub for a swarm,
public or private, to be held by M seeders for N days at a price per
GiB-month, bought over x402 exactly as a pass is. Seeders take leases,
prove each period by storage challenge or by a probe over the ordinary
wire, and are paid through the payee they already have. Public feeds ride
on ipdb; ipfile.pin on c0mpute is the same offer on the auction.
Also: the family table, stack diagram and registry rows; the ip seed
command group; PRD 0006; the protocol row on /openswarm.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKAohrRkqLKVQL2cGCAkR5
* Split pay2seed into client and server halves, and add pay2stream and paid2stream
The rule is now in the names. pay2* is the client protocol: the side that
pays, over HTTPS, and plays. paid2* is the server protocol: the BitTorrent
side that earns. One hub implements both halves of a pair; a requester or
viewer implements only pay2*; a seeder, relay or gateway only paid2*.
pay2seed keeps consent, offers, the requester's market and notices.
paid2seed takes leases, storage challenges and probes over the wire,
GiB-month accrual and receipts, the seeder client, and the ipfile.pin
mapping.
pay2stream and paid2stream do the same for a live channel over iplive.
A broadcaster attests the channel (with the two rules that separate a
licensed rebroadcast from a stolen feed), buys relays by the hour, and
publishes listings; viewers buy tickets. Relays take leases and are
proven present by a verifier that pulls segments as a peer; a gateway is
a relay that also serves standard HLS, clear or sealed, with the M3U and
XMLTV pair every IPTV app asks for, so VLC, TiviMate, Kodi and a
television play a paid swarm with nothing installed. Ace Stream showed
BitTorrent can carry live TV to millions; this is that with consent,
payment and an open spec.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKAohrRkqLKVQL2cGCAkR5
* pay2seed: encrypted by default, access as the product, a README in every swarm
The client encrypts by default and the hub never does: what the hub
manages is who may decrypt. A team is a named set of member keys with a
scope over the owner's swarms; the hub, as keeper, issues grants to
members when the owner is offline, with invitations that expire, roles,
an audit trail, and re-encryption on removal so the next version is
closed to whoever left. A few seats are free; above that the hub charges
per seat and per organisation, settled through the same pay plugins as
everything else. Seeding is priced at disk; access is where a hub earns,
and both sides earn: seeders rent disk, requesters sell access.
Public is not a fallback. Encryption off is an explicit act, and a
public swarm is attested, listed, kept and rendered exactly as a private
one is; the only difference is who can read it.
Every swarm on the market carries a README.md at its root, no
exceptions, and the attestation carries its Markdown and the hash of
the copy inside the swarm, so the hub renders it as the swarm's page
without a key. Relative links resolve into the swarm and are gated the
way the files are.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKAohrRkqLKVQL2cGCAkR5
* pay2seed: 1 percent, ads on the free tier, and agents as sellers
Three things the specs did not say. The reference hub takes 1 percent of
any payment that crosses it, charged to whoever is paying and never
deducted from a seeder or a relay, so a quoted price is what the
publisher gets and a promised floor is what the seeder is paid.
Public swarms are free to fetch and free to list, and an advertisement on
the swarm README page is what pays for that. The ad is on the hub page
and nowhere else: never inside a swarm, never injected into a file, a
segment or a playlist, and never in the catalogue or the market API. A
requester who wants no ad buys a seat instead. A free-to-watch channel
works the same way.
And a requester is a key, not a person. An agent can attest what it made,
price access, sell tickets, take payment through its own payee and spend
what it earns keeping its own work online. The consent rules do not
soften because a machine signed them, and the reference hub asks an
agent public attestation to name a responsible operator key so somebody
is reachable when a notice arrives.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKAohrRkqLKVQL2cGCAkR5
* Fix CI: number the PRD requirements and advance the next-id assertion
Two checks the new PRD tripped, both by existing rather than by being
wrong.
The collection validator wants requirements as numbered R# entries and
0006 used a plain ordered list, so it reported OP-L-NO-REQUIREMENTS.
Rewritten as R1 to R7 with priorities, one capability per entry, and the
implementation tracking moved to a paragraph under them where it is not
pretending to be a requirement.
The MCP standards test asserts what the next free PRD id is, and its own
comment says that advances with every PRD added. Adding 0006 makes it
0007.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SKAohrRkqLKVQL2cGCAkR5
---------
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
BitTorrent distributes bytes better than any CDN and has never been a
product: nobody is paid to seed, and nothing in it is private. Every
Profullstack media property answers that with a central HTTP proxy and a
pass system bolted on, and pays for every byte it serves.
OpenSwarm is an add-on to BitTorrent, carried as BEP 10 extension
messages, that fixes both. The swarm carries AES-256-CTR ciphertext whose
integrity is pinned by two SHA-256 merkle roots in a signed manifest, so
a tracker or DHT node learns an infohash and a size. A leecher buys a
pass over x402 in USDC (the same exchange x402-gateway runs for crawl
passes, settled by CoinPay), a seeder serves inside a bounded credit
window, and the leecher signs a cumulative voucher for every verified
batch. Whoever seeds gets paid. Vanilla clients remain valid members and
browsers remain first-class peers.
Adding a file mints a key pair for it, derived from one publisher seed by
default so there is one thing to back up: the public half is the file's
identity and its BEP 46 key, the private half signs the manifest and
authorises grants and payout changes, and a separate content key
encrypts the bytes and is sealed to paying peers.
The family: a core (records, keys, hashing, transports, discovery,
events), ipfile, ippay, ipdb (a signed hash-chained catalogue with heads
on the DHT), ipaudio, ipvideo, iplive (paid relays with backpressure)
and ipname (Moshpit pins and DNS TXT). Plus the c0mpute.com integration
with seven workload types and thirteen use cases, a proposed ip CLI,
conformance profiles, a security model and an FAQ.
Registered on the site the way OpenCreds is: nav entry, docs registry,
sitemap, a /openswarm landing page, PRD 0005, and the MCP prd_next_id
expectation moved to 0006.
Specs only. No code, no schemas, no reference implementation.
Claude-Session: https://claude.ai/code/session_01YafYxayh7Gqe5MWNNQMev2
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
* Add the LogicSRC OpenCreds specification
Leaving a password manager means writing every secret you own to disk in
the clear, and losing whatever the spreadsheet had no column for. A CSV
is plaintext by construction, lossy by omission, and carries no
integrity: nothing in it says which rows were meant to be there, so a
truncated import looks exactly like a complete one.
The same gap showed up inside LogicSRC. `logicsrc credentials` moves
.env secrets and SSH keys through end-to-end-encrypted team vaults, but
it can only model a key/value pair. A card, a passport, a login with a
TOTP seed, or an OAuth account with a refresh token are all things
people already keep in a vault, and none of them are a key/value pair.
OpenCreds defines three things: the item, the vault, and the database.
- Six item types (login, card, identity, note, key, account) as one
record with a type and a named field group, so everything the user
typed lives in a single encrypted blob. Codes 1-4 match MarkSyncr's
deployed vault and are not renumbered; compatibility is cheaper than
elegance.
- AES-256-GCM over that record with the item id bound as AAD. Without
it, anyone with storage write access could move a low-value login's
ciphertext into a high-value row and watch what the user does next.
- A key hierarchy where the user key is random, not derived, so a
password change re-wraps 32 bytes rather than re-encrypting a vault.
The auth hash comes out of a different HKDF label than the wrap key,
which is what lets it reach a server at all.
- A portable .opencreds file, encrypted by default, whose header is the
AAD over the payload -- so the manifest is authenticated by the same
tag as the data and a truncated import fails rather than reporting
success. The plaintext form exists because people move to products
that read nothing else; it is opt-in, confirmed, 0600, and labelled
"protected": false in its own header.
Namespaces are carried as data, not fixed by the spec: labels are
compiled into every ciphertext a vault has written, so editing one does
not migrate a vault, it makes it undecryptable. MarkSyncr's deployed
vault is conformant by declaring `marksyncr`.
Ships: prd/0004, nine spec pages under docs/opencreds/, six JSON
Schemas, the @logicsrc/opencreds reference implementation with CSV
importers for five products, `logicsrc vault` and the standalone
`opencreds` binary, and the spec page at logicsrc.com/opencreds.
`vault` rather than `creds` because `creds` is already an alias of
`logicsrc credentials`, and the two are different: one moves a pair
between providers, the other stores a record.
@logicsrc/validators now registers every schema by $id before
compiling, so the database schema can $ref the item and manifest
schemas rather than restating them.
120 tests, including CLI end-to-end coverage of the masking rules,
exit codes, and the manifest-mismatch path.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRQrfuwuYKKV5UB9kLHuX5
* Make the OpenCreds conformance claim executable
The conformance page described a fixture suite and an `opencreds
conformance` command that did not exist. A specification that documents
a conformance surface it cannot run is a specification nobody can hold
to, including us.
`opencreds conformance` now runs the requirement list as code -- one
check per C-number, carrying its own id and level -- and emits the
report shape the spec publishes. It exits 2 when a MUST does not pass,
so it can gate CI directly. The reference implementation reports 29
passed, 0 failed, 1 skipped; the skip is C19, because key management for
the team profile lives in @logicsrc/plugin-credential-sharing rather
than in this package, and a skipped MAY does not affect conformance.
Fixtures are generated (`--emit-fixtures <dir>`) rather than
hand-written. A vector produced by an implementation and then verified
by it is worth more than a JSON file someone typed: the typed file
drifts silently when the format moves, and the generated one cannot.
Fourteen files, including an invalid/ set every conforming reader must
reject -- a wrong field group, a weak KDF, an unregistered namespace, a
short payload and a tampered manifest.
The CLI requirements stay with the end-to-end tests that drive the real
binary through a child process; a command cannot meaningfully check its
own exit codes, and a masked value that is only masked in the library is
not masked.
conformance.md and cli.md now describe what ships.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRQrfuwuYKKV5UB9kLHuX5
* Add @logicsrc/opencreds to the lockfile
`npm ci` refuses a lockfile that does not match package.json, and the
new workspace package plus the CLI's dependency on it were never
recorded: the worktree was bootstrapped by hardlinking node_modules
rather than installing, so npm was never asked to update the lock.
Adds the workspace link and the package entry. No dependency versions
move.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRQrfuwuYKKV5UB9kLHuX5
* Register PRD 0004, and stop the fixtures looking like real secrets
Two CI failures, both mine.
`prd/README.md` is generated by `logicsrc prd index --write` and the
scaffold test asserts it is current, so adding a PRD without
regenerating it leaves the repo's own conformance check failing.
Regenerated. The MCP test asserts the next free PRD id against the live
prd/ directory — its comment says it advances with every PRD added — so
it moves to 0005.
ThreatCrush flagged three of the example strings: a PEM header in the
item-model docs and in the conformance fixture, and an `sk_live_`
prefixed token. All placeholders, none real, but the finding is the
scanner working. A fixture only has to exercise the field, and a
real-looking private key header or live-key prefix sitting in the tree
trains both the scanner and the people reading its output to shrug at
exactly the shape that matters. Replaced with obvious placeholders
rather than suppressing the rule.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QRQrfuwuYKKV5UB9kLHuX5
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Add SSH keys and config to credential sharing
Private keys have lived as plaintext-on-disk files guarded only by a
passphrase. This puts them in the same end-to-end-encrypted vaults as
.env secrets, and adds an agent path so a machine can use a key without
ever writing one to its disk.
- `ssh` provider: ~/.ssh as a value bag. Files are picked by sniffing
contents (PRIVATE KEY blocks, ssh-*/ecdsa-*/sk-* public keys) plus
config, config.d/* and allowed_signers. known_hosts and
authorized_keys are host-specific and access-granting, so they need
an explicit --include.
- Each file is one secret carrying a JSON envelope of path, mode and
body. The engine only hands write() the secrets that CHANGED, so a
separate manifest secret would be absent whenever a key's contents
change but the file list doesn't — self-describing values keep every
restore total.
- `logicsrc secrets ssh push|pull|list|agent`, addressed by PERSON not
project: the vault is ssh--<username>, which teams vaults reads as
project ssh, env <username>. One teammate's keys never land in
another's restore; sharing stays a deliberate teams grant.
- Both directions hold back anything that would overwrite a file that
already differs, and say what they skipped. --force opts in. A
restore onto a machine with its own keys is otherwise a way to lose
them.
- Restores chmod each file back to its recorded mode; writeFileSync's
mode applies only on create, so an existing world-readable key would
otherwise stay world-readable. The adapter declares delete:false.
- push warns about passphrase-less private keys before they go up.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Add worked examples to secrets and secrets ssh help
Commander's usage line shows only the first alias, so `logicsrc secrets`
— the spelling people actually type — was invisible in its own help.
The examples carry it, alongside the flows worth copying: link/up/down,
the ssh backup round trip, and a plan → dry-run → approve sync.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Advertise the ssh provider on the marketing page
The marketing-drift contract failed the build because `ssh` shipped in the
provider registry with no entry in MARKETING_PROOF -- which is the test
working: it exists so a provider cannot ship while the pages people
actually land on still describe the tool without it.
The proof regex is `/~\/\.ssh|SSH key/` rather than a bare `/SSH/` on
purpose. The provider grid renders every registry `name`, and this one is
"Local SSH directory", so `/SSH/` would already be satisfied by the
generated grid and the provider could ship with no copy written about it
at all -- passing the test while failing its intent. Requiring the path or
the phrase means a human wrote a sentence.
That sentence is the new block in the credential-sharing band: ~/.ssh is a
directory of files whose permission bits are load-bearing, not a set of
KEY=VALUE lines, which is the part that makes this provider different from
the other six. README already named ~/.ssh keys, so it needed no change.
apps/logicsrc-web: 75/75 contract tests pass (was 74 passed, 1 failed).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@logicsrc/opencontext could not be installed from npm. Three defects, each of
which alone breaks a published tarball:
1. opencontext depended on "@logicsrc/validators": "file:../validators". A
file: specifier is unresolvable for anyone installing from the registry, so
`npm install @logicsrc/opencontext` failed outright.
2. validators imported all 50 schemas by relative path across the repository
("../../schemas/schemas/*.json"). That resolves inside the monorepo and
escapes the package once published, so an installed validators could not
load a single schema. Now imported through @logicsrc/schemas package
exports, with a real dependency declared.
3. Three of those schemas — repo, pull-request, openprd-prd — had no entry in
the schemas exports map, so they were unreachable by package specifier.
Added; the map is now sorted so it stays readable as it grows.
validators also gained files/publishConfig/license so it publishes the same way
its siblings do.
Verified the way a stranger would: npm pack all three, install the tarballs
into a clean project outside the monorepo, and run the installed binary —
version, init, validate --strict (which exercises schema loading through the
package exports), and resolve --explain all succeed. Full workspace suite green.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Add the LogicSRC OpenContext specification
OpenContext is an open specification for durable, portable, permissioned,
provenance-aware context shared between humans and AI agents. It defines how
organizational knowledge is described, authorized, versioned, resolved,
audited, and handed between replaceable workers without losing institutional
state.
Follows the OpenPRD/OpenOntology pattern already in the repo: self-contained
JSON Schemas in @logicsrc/schemas, a reference implementation package, CLI
subcommands, docs, examples, and an OpenPRD record.
Schemas (8, all self-contained so a third party can fetch one file and
validate against it with no further resolution):
manifest, object, bundle, role, provenance, decision, diagnostic,
audit-event — registered in @logicsrc/validators and schemas:validate.
Reference implementation (@logicsrc/opencontext):
loader with upward manifest discovery, the full resolution pipeline,
authority/supersession, permissions, redaction, lifecycle, provenance,
deterministic digests, doctor, search, graph, history/diff, guarded writes,
audit events, and file/http/git/sqlite adapters.
CLI: all 15 specified commands, as a standalone `opencontext` binary and as
`logicsrc context`, sharing one implementation so the two cannot drift.
Design decisions worth noting:
- Supersession is declared, never inferred from version numbers. Inferring it
would hide the governance failure it represents and make
multiple-active-versions and duplicate-canonical impossible to detect.
- The bundle digest identifies the resolved context, not the moment it was
computed, so generated_at/bundle_id/digest/as_of are excluded while objects,
lifecycle states, exclusions and warnings are covered. That is what lets a
decision record cite exactly the context that produced it.
- A role's own max_classification beats an inherited one, so a ceiling on a
shared base role cannot silently cap a role deliberately granted more;
requesting several roles at once still takes the lowest, so combining roles
never escalates.
- Scope wildcards match whole dotted segments only. A trailing .* covers a
subtree; an interior * matches exactly one segment. Substring matching here
would be an access-control bug.
- --include narrows an existing scope and is applied after it, never merged
into it, so a request can never widen what a role holds.
Verified: 226 tests across core primitives, permissions/redaction, the
resolution pipeline, security, the published conformance fixtures (13 valid,
35 invalid, 8 resolution scenarios), project behaviour, and the five shipped
examples — which are held to --strict and a 100% health score. Benchmarks meet
every published budget (resolve 1,000 objects in ~33ms against a 2s target).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Point install docs at @logicsrc/opencontext; record the npm name collision
The unscoped `opencontext` name is already published on npm by an unrelated
third party (federicodeponte/opencontext, 2.0.0), so `npx opencontext` would
install a stranger's package. Docs now use `npx @logicsrc/opencontext`; the bin
stays named `opencontext` so the command reads as the PRD specifies once
installed.
Recorded in PRD 0003 as a blocker to resolve before any publication, along with
the fact that no @logicsrc spec package has ever been published, so there is no
existing release path to slot into.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* Advance the logicsrc-mcp next-PRD-id assertion to 0004
standards.test.ts asserts prd_next_id against the live prd/ directory, so
adding PRD 0003 makes the next free id 0004. The test's own comment
anticipates this: "advances with every PRD added".
Caught by CI, not locally — the earlier verification ran per-package tests for
the packages this branch touches, and logicsrc-mcp is coupled to the PRD
directory without importing from it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Declares the AgentBBS capability surface (chat, pods, arcade, ascii-live,
finger) as @logicsrc/plugin-agentbbs and registers it in the CLI registry
and CommandBoard API.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
CI failed on an unrelated PR when "seeds a package and hydrates it back
identically" passed vitest's default 5s timeout. The assertions were
fine; the suite is I/O bound and the runner was slow. Two changes.
migrate() ran every DDL statement through its own client.execute(), so
migration 1's ~30 statements each became a separate durable commit and
opening a store paid ~30 fsyncs. Batch each migration into one write
transaction instead: locally a fresh migration drops from ~8.2ms to
~5.0ms, and the gap widens as fsync gets more expensive. It also closes
a real hole -- a crash part-way could previously leave the schema
half-applied while schema_migrations recorded the migration as done,
because the statements and the bookkeeping insert were not atomic.
Then give the package a 30s testTimeout. These suites drive a real
file-backed SQLite database, so their wall time is set by the host
filesystem, not by our code. The 5s default is tuned for CPU-bound unit
tests and leaves no headroom on a contended runner.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
The credential store resolved its base directory against process.cwd().
Running the CLI from inside a git checkout wrote `.logicsrc/credentials`
into that repo's working tree — a directory containing `vault/`, the one
place raw credential values touch disk — untracked, unignored, and one
`git add -A` from being committed. Two such directories were sitting in
unrelated repos on the machine this was found on.
A per-directory store is also the wrong shape for what the store is for.
It is the record of what was rotated and what the prior values were, and
a record that forks per project folder is several records that disagree.
There is one user, one identity, one vault.
Everything now hangs off a single logicsrcHome(): $LOGICSRC_HOME, else
$XDG_CONFIG_HOME/logicsrc, else ~/.config/logicsrc. The credential store,
the identity and the CLI config all read it rather than each deriving
their own answer — three separate derivations is how the vault ended up
somewhere the config never was.
~/.logicsrc is migrated rather than abandoned. It holds the X25519 secret
key, and losing that loses access to every team vault the member was ever
given, so it is moved on first use; a move that fails says so on stderr
instead of leaving someone silently logged out with a key still on disk
somewhere they were not told about. If the new directory already exists
it wins and the old one is left untouched, because two directories both
claiming to be the identity is how a login writes one and a read finds
the other.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two additions to Credential Sharing.
`logicsrc credentials rotate` (alias `logicsrc secrets rotate`) re-keys a
team vault: fresh DEK, re-sealed to the members who keep access, every
secret re-encrypted under it. Values do not change, so nothing that
consumes them breaks; what changes is that every wrapped key issued
before the rotation is dead. --active (the default) keeps only active
members and revokes the rest -- the "someone left" rotation. --all keeps
everyone who holds access, for plain hygiene. Dry run by default, like
`sync`.
The DEK is recoverable ONLY through the grants, so a half-applied
rotation makes a vault permanently unreadable by everyone. The whole next
state therefore goes to the server in one request and commits in one
transaction (new db.batch helper). The server also requires every
submitted fingerprint to equal the stored one: it cannot see values, but
it can prove a re-key did not swap any. Rotations that would leave the
caller ungranted, grant nobody, or cover the wrong secret count are
rejected before anything is written. GET /vaults/:id/grants now returns
publicKey and status so a client can re-seal in one pass instead of N+1
user lookups, and revocation finally deletes the grant row rather than
leaving one that reports access it no longer confers.
The sh1pt adapter is the fifth provider. It is the only one driven
through a CLI rather than HTTP, because sh1pt publishes
`sh1pt secret set|get|list|rm` as the interface to its vault and
documents no REST endpoint. Values go over the child's stdin, never argv
-- a secret in argv is readable by any user on the host via ps. Since
`sh1pt secret get` needs interactive confirmation it cannot be scripted,
so the adapter is write-only for values like github-secrets: a sync
target, never a source, no value-restoring rollback.
Tests drive a real fake sh1pt binary rather than a mocked execFile, which
is how the hang surfaced: with nothing to pipe, stdin was left open and
any subcommand that reads it would wait forever. It is now always closed.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
#109 addressed vaults as <project>/<env> and shipped broken: every push
failed with
Vault name must be lowercase letters, numbers, and dashes.
The vault-create endpoint slugifies through /^[a-z0-9][a-z0-9-]{0,62}$/
(apps/pwa/src/routes/credshare.mjs), so a "/" join is refused outright.
Nothing in the CLI ever saw it, because the tests exercised vaultName and
splitVaultName in isolation and never made a request — the one assumption
that mattered, that the server takes an arbitrary vault name, was the one
left unverified.
Switches the separator to "--", which is inside the allowed character set
and still splits unambiguously since neither half may contain one. A
single dash would not: "a-b" + "c" and "a" + "b-c" would collide.
Also validates the joined name against the server's own regex before the
request, so a bad name fails locally with a useful message rather than a
422 after the .env has been read.
The tests now assert the produced name matches that regex, so the
separator cannot drift back out of the allowed set without failing.
Verified end to end against app.logicsrc.com: push, then pull into a
scratch file and diff — keys and values both round-trip losslessly. Then
49 repos pushed under the profullstack team; server reports 49 vaults,
169 secrets, 0 failures.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
The "Connect the CLI" card handed out:
LOGICSRC_API=https://app.logicsrc.com logicsrc login
logicsrc teams push <team> prod --env .env
logicsrc teams pull <team> prod --env .env
Two things are wrong with that, and both survived a release.
Since #109 addressed vaults as <team> <project> <env>, push and pull take
three positionals. The hint passes two, so pasting it exits with a missing-
argument error -- the card is not merely stale, it is broken.
The LOGICSRC_API prefix sets the variable to the value the CLI already
defaults to (DEFAULT_API_URL, #107), so on the hosted app it does nothing
while reading like a required step. It is now emitted only when the origin
is not the default, which is the case it exists for: self-hosting.
`--env .env` is dropped for the same reason -- it restates the option's own
default, and sitting next to the new <env> positional it made one flag and
one argument look like the same thing.
Same stale two-argument form fixed in the post-install hint (install.sh) and
the accept-invite message, and in the empty-vault-list prompt on the card.
CLI_HINT moves to src/lib/cli-hint.mjs so a test can assert on the rendered
commands without standing up express and the database, matching how the
other lib-level views are covered. The tests pin the argument count rather
than the prose: restyling the card stays free, dropping an argument does not.
apps/pwa: 13/13 pass.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`teams push|pull|grant` took a single `<vault>` name, so a team holding
more than one project had to encode both halves by hand and hope
everyone spelled it the same way. They now take `<project> <env>` and
join them into the `project/env` vault name.
The split lives entirely in the CLI — vaultName()/splitVaultName() are
the only things that know about it, and the server still stores one
opaque vault name — so there's no migration. Both halves reject a "/"
so the join stays unambiguous and the split is a true inverse.
`teams vaults` now breaks the name back into project/env columns,
falling back to the raw name for vaults created before the convention.
Those legacy vaults are no longer addressable (their names don't
contain a slash), so resolveVaultId() lists what the team actually has
instead of just saying "not found" — better than silently retargeting a
push, which in a secrets tool would write to the wrong vault.
Note push/pull carry two different "env"s: the `<env>` positional is
the environment half of the address, `--env` is the local .env path.
Verified commander keeps them separate.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
`update` was three hardcoded console.log lines: it printed 0.1.0 as both
current and latest, claimed "already up to date", and never checked or
installed anything. `--version` was hardcoded the same way.
A version comparison alone could not have worked either. install.sh ships
a tarball of the master branch, not a tagged release, and
packages/cli/package.json has been 0.1.0 since the repo began, so version
equality says "up to date" no matter how far master has moved. The commit
is the real signal.
- install.sh records ref/commit/version/installed_at to
$LOGICSRC_HOME/install.json. The sha comes from GitHub's
Accept: application/vnd.github.sha media type, so this needs no jq.
It is resolved before the download on purpose: if master moves
mid-install we under-report (a spurious update) rather than falsely
claim to be current.
- update compares the installed commit against the remote ref head,
falls back to version comparison for installs predating the manifest,
and reports why it reached its verdict instead of just asserting one.
--check reports without installing; otherwise it re-runs the installer.
- --version now reads the package's real version.
Verified against live GitHub in all three states: matching commit, stale
commit, and no manifest.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`logicsrc login` defaulted to http://localhost:4010 — a dev origin that
doesn't exist on an installed machine, so the printed authorize URL went
nowhere. It now defaults to the hosted credentials app (apps/pwa), reads
the documented $LOGICSRC_API, and only reuses a stored apiUrl once that
identity has actually completed a login (which is how machines got stuck
pointing at localhost). Note logicsrc.com is the marketing site and has
no /cli routes.
The loopback flow is also unusable over SSH: redirect_uri is
http://127.0.0.1:<port>/callback, which resolves to the *browser's*
machine, not the CLI's. Added a device-authorization flow — the CLI
prints a short user_code, the human approves it from any browser:
POST /cli/device/code mint device_code + user_code (10 min TTL)
GET /cli/device approve page (login required; typo-tolerant)
POST /cli/device approve/deny (CSRF-guarded browser form)
POST /cli/device/token CLI polls -> lsk_ API key
device_code is stored sha256-hashed, single-use, with authorization_pending
/ slow_down / access_denied / expired_token poll semantics. The CLI picks
the flow automatically (SSH/CI/no-DISPLAY -> device), with --device/--web
to force it and a fallback to loopback against servers without /cli/device.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat(web): move Hire Us pricing to $400/hour metered billing (PRD 0002)
Replaces the $250/week retainer with a $400/hour rate billed against actual
hours, invoiced through CoinPay after the client approves them. A 10-hour
minimum engagement replaces the week as the unit of commitment.
The weekly price lived in 12 places, not the 3 the PRD listed: the front-page
Hire Us section, the Top-Level Pages list, /hire-us metadata, /pricing
(metadata, two FAQ answers, rate bullet), /about, llms.txt, skill.md, and the
Hire Us form success message.
Metered billing rather than a committed weekly block, because the old
"recurring CoinPay invoice" copy documented a mechanic that never existed:
/api/payments/create makes a single one-shot payment, not a subscription.
- coinpay-checkout derives amount_usd from hours x 400 instead of a hardcoded
250, validates hours as quarter-hour increments at or above the minimum, and
returns 422 before calling CoinPay on bad input. Payment metadata carries
billing/hours/rate_usd_per_hour in place of interval.
- project-request returns a rate, billing mode, and minimum; no amount exists
until hours are approved.
- CoinPay config block documents COINPAY_RATE_USD_PER_HOUR / COINPAY_BILLING /
COINPAY_MINIMUM_HOURS instead of a weekly amount and interval.
- New real /terms route replacing the SPA stub: what is billable, the
approve-then-invoice flow, the minimum, cancellation on one week's notice,
and an explicit clause that existing engagements keep their terms until both
sides agree in writing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* fix(mcp): advance prd_next_id expectation to 0003 for PRD 0002
The standards test asserts prd_next_id against the live prd/ directory, so
adding prd/0002-hourly-hire-us-rate.md moves the next free id to 0003. This
assertion advances with every PRD added to the repo.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Everything the two shipped PRD phases deferred, minus what is called out below.
Storage (Phase 2)
@logicsrc/openontology gains a SQLite/Turso adapter. It hydrates the read
model at open, serves reads synchronously — a query evaluator that awaits per
triple pattern is unusable — and buffers mutations as SQL that flush() writes
in one transaction. Versioned idempotent migrations; indexes over subject,
predicate, entity-valued object, status, both time axes, aliases, and external
ids; FTS5 for label/alias search. The append-only status log is replayed on
open, so retractions, supersessions, and merge redirects survive a reopen.
REST + SSE + OpenAPI (Phase 2)
16 paths under /api/ontologies in logicsrc-web, described at
/api/ontologies/openapi and referencing the published JSON Schemas rather
than restating them. No token is read-only; a curator token can apply; an
agent token can propose and cannot apply. Idempotency-Key on mutations,
revision ETags, 409 on a stale base revision, and an SSE stream that emits
the same event objects as the JSON endpoint.
MCP (Phase 2)
OpenOntology and OpenPRD surfaces on the standards server: spec/manifest/
schema/queries and PRD spec/index as resources, 11 ontology tools and 6 PRD
tools, 7 prompts. Read-only by default; OPENONTOLOGY_MCP_WRITABLE=1 buys
proposals, never applies — the denial is the shared policy layer, not a
second rule that could drift.
Interoperability (Phase 3)
RDF/Turtle export and import of the reified profile, plus the plain triple
for asserted relationships so a consumer wanting only the accepted graph gets
one. SHACL for 5 of 7 constraint kinds; `unique` and `query` are reported as
unmapped in both the return value and the generated Turtle, because a shape
that quietly means something narrower is worse than no shape.
Source adapters (Phase 3)
CSV, JSON, YAML, NDJSON, Markdown, generic JSON HTTP, and GitHub. All produce
PROPOSED change-set operations with source, evidence selector, run id, and
confidence attached; fetch is injected so ingestion is offline and testable.
Each declares its capabilities, so "nothing was deleted upstream" is never
confused with "this adapter cannot see deletions" — none of the seven can.
TUI + explorer
Keyboard-first panels (types, entities, claims, sources, queries, change
sets, validation, audit) as plain strings that survive SSH and 60 columns;
status is a glyph and a word, never colour alone; the key bar wraps rather
than truncating. Wired as `logicsrc ontology tui`. A read-only web explorer
at /openontology/explore with entity and claim views showing status, both
clocks, confidence, sources, evidence, and append-only history — plus an
/openprd page for the companion standard.
Bugs found and fixed while testing
- the API built a new engine per request, so `explain` could never find a
resultId from a prior request; engines are now cached per role
- the TUI status bar called engine.validateOntologyPackage(), appending a
package.validated event on every repaint; it now uses the pure validator
Verification: 76 new tests (527 total across the monorepo, all passing); full
build green; the libSQL adapter is exercised against real files, the API
through its route handlers, and MCP over an in-memory transport.
Not included: PWA review/approval write flows (they need an auth story this
deployment does not have), OWL/RDFS mappings, SPARQL/Cypher/Datalog query
adapters, and Phase 4 governed actions. The compatibility matrix marks those
"planned", not "supported".
Refs: prd/0001-add-logicsrc-openontology-spec.md
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
OpenPRD has existed as a document (docs/openprd.md), a front-matter schema, a
template, and this repo's prd/ collection. Nothing enforced it. This adds the
reference implementation.
@logicsrc/openprd
- parser: front-matter + the eight `##` sections + numbered requirements.
`###` stays content so a long Requirements section can be organized, and
headings or R#-shaped lines inside code fences are ignored
- validation splits the standard's four conformance rules (filename,
front-matter schema, id-matches-prefix, eight sections in order) from
lint (empty section, missing priority tag, numbering gaps, duplicate R#,
date order, one-sided supersession, stale index). Conformance failures are
errors; --strict promotes the rest. Stable codes, file, line, hint
- collection rules the per-file view cannot see: unique ids, monotonic
numbering with no gaps, 0000 reserved for the template, cross-references
that resolve
- lifecycle enforced rather than advisory: Draft cannot jump to Final,
terminal statuses do not resume, Superseded must name its replacement
- deterministic index generation, so `prd index` is idempotent and CI can
diff it
- front-matter rewriting that leaves the body byte-identical
- the optional LogicSRC task bridge the standard describes: each R# becomes
one logicsrc.task, validated against logicsrc-task.schema.json before it
is emitted; creator DID derived from the author email
CLI: logicsrc prd init|new|list|show|validate|lint|index|status|next|tasks|
export. Exit codes stable for CI (0 ok, 1 invalid, 2 usage, 3 not found).
Conformance bundle: packages/schemas/fixtures/openprd/ — 6 documents that must
validate and 12 that must fail, each naming the error code it must produce.
Several rules depend on the filename, so every fixture records the name it is
validated as.
Docs: an Implementation section in docs/openprd.md (CLI, validation model,
task bridge, conformance bundle), the spec added to the site's docs surface,
nav and sitemap entries, and a README section.
Verification: 76 new tests; full monorepo build and all 451 workspace tests
pass. The suite dogfoods this repo — prd/ validates with zero errors and zero
warnings, the embedded template is byte-identical to docs/openprd/0000-
template.md, and all 210 requirements in PRD 0001 map to schema-valid tasks.
prd/README.md is regenerated by the tool it now ships.
Refs: docs/openprd.md
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Implements OpenPRD 0001 through Phase 0 (specification, schemas, example,
docs surface) and Phase 1 (local engine, CLI, conformance tests).
Schemas (17 contracts, JSON Schema Draft 2020-12, additionalProperties:false)
manifest, namespace, entity-type, property, relationship-type, constraint,
query, action, entity, claim, source, evidence, changeset, review, approval,
event, package — registered in @logicsrc/validators and exported from
@logicsrc/schemas under https://logicsrc.com/schemas/openontology/.
@logicsrc/openontology
- canonical JSON + sha256 package digests; YAML, JSON, NDJSON, and inline
authoring all compile to the same bytes, so digests are authoring-agnostic
- id profile: compact / IRI / urn with one canonicalization rule, prefix
bound by a Namespace object so IRIs reverse unambiguously
- validation: schema, graph (domain/range, datatypes, dangling refs),
provenance (source-or-firstParty, agent runId, derivation inputs), policy
(excerpt limits, licensing, visibility, staleness) and declared
constraints; four severities, stable codes, text/json/yaml/markdown
- portable triple-pattern query AST: multi-hop, 14 operators, asOf and
recordedAsOf, per-status filtering, distinct/order/limit, explanation
mode, and enforced depth/binding/row limits
- append-only store: claims are immutable; dispute/retract/supersede append
status transitions and the effective status is the latest one
- change sets: 9 operations, atomic pre-flight, conflict detection on stale
base revisions, semantic diff with duplicate-identity warnings and
affected-query deltas, per-operation reviewer decisions
- policy: agents propose but can never apply — the denial keys on actor
type, so every scope plus high confidence plus --yolo still cannot apply;
merges need approval, bulk retractions need two, undeclared action side
effects are denied
- JSON-LD 1.1 export/import with PROV-O aliases and lossy-field reporting
- pluggable signature envelope with a jws-ed25519 reference profile and a
fail-closed trust policy
CLI: logicsrc ontology init|validate|lint|build|inspect, entity, claim, query,
changeset, import, export, audit. Reads take --format, writes default to a
proposal, exit codes are stable for CI.
Example: examples/openontology/ethereum-ecosystem — 12 entity types, 17
relationship types, 63 entities, 169 claims, 25 sources, 31 evidence records,
5 saved queries, every claim lifecycle state, and a pending merge proposal.
All data is fictional; the directory is removable without affecting any core
test.
Docs: docs/openontology{,-governance,-interoperability}.md, a real
/openontology route, homepage + nav + sitemap entries, and a root README
section.
Verification: 112 new tests; full monorepo build and every workspace test
pass; conformance bundle (18 valid + 13 invalid fixtures) runs against the
published schemas alone; Node.js 25 and Bun 1.3 produce byte-identical
digests, revisions, event trails, and query results.
Not included (later PRD phases): MCP resources, REST/SSE, Turso adapter, TUI
and PWA surfaces, RDF/SHACL mappings, source adapters, governed actions.
Refs: prd/0001-add-logicsrc-openontology-spec.md
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds apps/pwa: an Express + libSQL/Turso app that is now the home of team
credential sharing, with the moshcode-style auth stack ported and reskinned to
match logicsrc.com (light theme, Inter, green accent).
apps/pwa
- auth: email/password (scrypt), passkeys (WebAuthn), CoinPay OAuth, cookie
sessions, and lsk_ API keys for the CLI via a loopback OAuth-PKCE flow
(/cli/authorize + /cli/token). Ported from the moshcode PWA.
- credshare API (/api/credshare/*): teams, members, invites, vaults, sealed
grants, ciphertext secrets, audit — authed by session OR Bearer lsk_ key.
Zero-knowledge: only ciphertext + sealed vault keys + public keys stored.
- teams dashboard, accept-invite, and settings (API keys) pages, server-rendered
in the LogicSRC brand (lib/html.mjs).
- migrations (libSQL) 001_auth + 002_credshare, migrate-on-boot; Turso via
TURSO_DATABASE_URL / TURSO_AUTH_TOKEN, or a local file db for dev.
- trimmed moshcode-specific approvals/credits/push/deliver.
CLI
- `logicsrc login` now does browser loopback OAuth-PKCE against the app and
stores an lsk_ token (email-OTP removed); --token for CI. Client repointed.
Distribution
- install.sh (served at logicsrc.com/install.sh) installs the CLI from the
GitHub repo: tarball -> npm install -> `npm run build:cli` -> logicsrc wrapper.
- root build:cli builds only the CLI's workspace chain (skips web/api/next).
Cleanup
- removed the commandboard-api credshare backend (superseded by the PWA) and its
Supabase/Turso stores + libsql dep; commandboard-api tests green (40).
- removed the Next.js /teams page (the PWA is the web UI now).
Verified end-to-end: two accounts register on the PWA, mint lsk_ keys, CLI login
uploads identity keys, owner pushes an encrypted .env, teammate invited ->
accepted -> granted -> pulls the exact file. Server stores ciphertext only.
Full workspace build + tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds a `team` credential provider + team/member management so teammates can
share secrets by email instead of passing .env files over chat. Fully E2E:
the server only ever stores ciphertext, per-member sealed vault keys, and
public keys — it never sees a plaintext value or the vault DEK.
Plugin (@logicsrc/plugin-credential-sharing)
- crypto.ts: X25519 identity keys, per-vault DEK (secretbox), DEK sealed to
each member's pubkey (crypto_box_seal), value encrypt/decrypt (libsodium)
- identity.ts: local ~/.logicsrc/identity.json (0600) holding the device key
+ API token; never uploads the secret key
- client.ts: typed /api/credshare client
- providers/team.ts: `team:<slug>/<vault>` CredentialProvider (inspect,
readValues=decrypt, write=encrypt, rollback); fingerprints match env so
env<->team diffs line up
- fixes latent libsodium-wrappers ESM load bug (createRequire) here + in
github-secrets
Server (commandboard-api /api/credshare)
- zero-knowledge router: email-code auth, keys, teams, members, invites,
vaults, sealed grants, ciphertext secrets, audit; membership authz in app
- CredShareStore abstraction: in-memory (dev/tests) + Supabase (prod)
- Resend email transport for login codes + invites (no-op -> echoes locally)
- supabase migration: credshare_* tables, deny-by-default RLS
CLI
- real `logicsrc login` (email code -> token + key upload)
- `logicsrc teams create/list/invite/accept/members/vaults/grant/push/pull`
Web (logicsrc.com/teams + /teams/accept)
- management surface only (browser holds no private key, never decrypts):
login, view teams/members/vaults, invite, accept
Tests: crypto round-trip, server contract (invite->accept->push->grant->pull
+ authz boundaries), and a real HTTP+client+crypto E2E asserting the server
never holds plaintext. Full workspace build + tests green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-up to #96. The template and tool-generated PRDs (moshcode /prd) ship
optional keys blank; YAML reads them as null. Accept null on owner/repo/created/
updated/discussion/implementation/tags/supersedes/superseded-by/authors so
template-derived files validate against the schema.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Reshape OpenPRD from a private single-file (prd/<slug>/prd.md, gitignored) into a
published, numbered proposal collection like BIP/EIP/DIP: prd/NNNN-slug.md +
0000-template.md + a README index, committed to the repo, with a lifecycle
(Draft → Review → Accepted → Final; Rejected/Withdrawn/Superseded).
Tools (e.g. moshcode /prd) consume this to publish PRDs into whatever repo the
user is working in.
- docs/openprd.md — rewritten: numbering, lifecycle, directory layout, conformance.
- docs/openprd/0000-template.md — the canonical template.
- packages/schemas/schemas/openprd-prd.schema.json — 4-digit id, status enum,
authors, discussion/implementation, supersedes/superseded-by.
- fixture updated to 0.2.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
validate() was creating a new AJV instance and recompiling the schema on every
invocation. Schema compilation is expensive (JSON Schema parsing, format setup).
Cache the compiled validator per SchemaKind so compilation happens once.
OpenPRD is a lightweight, single-file PRD standard (prd/<slug>/prd.md) for
humans and AI agents — the low-ceremony counterpart to OpenSpec's multi-file
change bundles. PRD documents are private by convention; only the standard is
published here.
- docs/openprd.md — the standard: file layout, front-matter, the 8 required
body sections, privacy, and the optional bridge to LogicSRC tasks.
- packages/schemas/schemas/openprd-prd.schema.json — front-matter manifest schema.
- packages/schemas/fixtures/openprd-prd.yaml — a valid manifest fixture.
- packages/validators — register the openprd-prd schema.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
fromHex validated only that the string was even-length, then used
parseInt(pair, 16) per byte. parseInt returns NaN for a non-hex pair, and a
Uint8Array stores NaN as 0 — so fromHex('zzzz') silently returned [0, 0]
instead of failing. Add a hex-charset check (allowing empty input) so bad
input throws. Add bytes.test.ts covering valid decode, round-trip, odd-length
and non-hex cases.
New @logicsrc/plugin-credential-sharing: a provider-neutral secret-sync engine
with env/.env, Doppler, Railway, and GitHub Secrets adapters behind one
CredentialProvider contract.
- engine: inspect -> diff -> plan -> approve -> sync -> rollback -> audit/export
- dry-run is the default for sync; --approve writes; destructive changes gated
- fingerprint-based diffs (salted SHA-256); raw values never printed or stored in
plans/runs/audit; rollback pre-image kept in a 0600 .logicsrc vault (gitignored)
- github-secrets is write-only for values (sealed-box via libsodium), so it cannot
be a sync source or value-restoring rollback target
- CLI: real `logicsrc credentials <providers|inspect|diff|plan|approve|sync|
rollback|audit|export>` (replaces the prior stub)
- 4 JSON schemas registered in @logicsrc/validators
- flip logicsrc.com/credential-sharing band from coming-soon to available
- 37 tests pass; full env->env lifecycle verified; artifacts schema-validate
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add files/publishConfig(public)/repository/keywords + prepublishOnly build;
exclude *.test.ts from the tsc build so dist (and the published tarball) ships
only library code. Package size 17 kB, 38/38 tests still green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
withX402() wraps the Web handler so unpaid calls get a standard HTTP 402
challenge (x402Version + accepts[]) and paid calls fall through to the agent.
Payment verification is injectable (host wires CoinPay/x402); CORS preflight
passes through untouched; custom challenge responses supported. README documents
the optional c0mpute.com GPU backend.
4 new tests; 38/38 pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
createC0mputeModel() turns a connected c0mpute.com account (apiKey + OpenAI-
compatible base URL) into a LangChain chat model, usable as the model for
createDeepAgentRunner or the chatModel for the judge/router — so agent inference
can run on community-shared GPUs. Opt-in; default stays the hosted provider.
agentswarm performs no OAuth: the host's c0mpute connector supplies credentials
(resolveC0mputeConnector / c0mputeConnectorFromEnv normalize them). DeepAgent
runner now accepts a model instance, not just a provider string.
7 new tests (defaults, env mapping, not-connected + missing-peer errors); 34/34 pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
BudgetLedger interface + InMemoryBudgetLedger reference impl (reserve/settle/
release with refund of unused holds; mirrors the b1dz budget+ledger concept).
withBudget() wraps any SwarmRunner: reserves up front (throws SwarmError 402 when
out of funds, before running), settles actual cost on success, releases on error.
AgentIdentity DID aligns with @logicsrc/agentstack.
8 new tests; 27/27 pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
createSwarm() composes named agents into one SwarmRunner: a router picks the
first agent; any agent hands control to a named peer via `HANDOFF: <name>`,
bounded by maxHandoffs. createLLMRouter() is a cheap-model router. Composes
with the rubric loop and HTTP handler since a swarm is itself a SwarmRunner.
6 new tests (routing, handoff, maxHandoffs cap, unknown-peer/unknown-route
fallbacks, empty-agents guard); 19/19 pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@logicsrc/agentswarm: a self-hosted, framework-agnostic multi-agent runtime
that each app mounts on its own route (e.g. tronbrowser.dev/swarm).
M1 — core:
- createSwarmHandler(): Web (Request)=>Response handler, CORS + validation
- SwarmRunner: injectable engine interface
- createDeepAgentRunner(): deepagents (createDeepAgent) adapter; deepagents +
@langchain/langgraph are optional peers loaded via dynamic import so the core
builds/tests with zero heavy deps
- SwarmError + onRequest gate: seam for x402 metering / auth (402/403)
M3 — rubric self-check (deepagents RubricMiddleware is Python-only, so ported
at the runner layer):
- createRubricRunner(): grades output via an injectable judge and revises until
it passes or maxIterations; passes through untouched when no rubric
- createLLMJudge(): cheap-model grader (lazy langchain initChatModel)
13/13 vitest pass; tsc clean; examples/server.mjs demo verified.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
M1 of the ANS SDK (docs/ans-sdk.md):
- name: parse/format ans://v<semver>.<agent>.<domain>
- cbor: minimal RFC 8949 codec for the COSE_Sign1 subset
- verify/merkle: RFC 6962 leaf/node hashing, tree build, inclusion-proof
generation + verification
- verify/es256 + cose: COSE_Sign1 build/parse, Sig_structure, ES256 (WebCrypto)
- verify/rootkeys: kid -> verifier key (JWKS entries; sumdb-note is M2)
- verify: verifyReceipt() + verifyResolution() (signature + inclusion proof +
name binding), pure and offline
- client: AnsClient resolve/rootKeys/register/status over injectable fetch,
with a DnsApplier hook for verify-dns
Tests (24) cover name parsing, CBOR round-trips/vectors, RFC 6962 proofs, a full
ES256+Merkle receipt round-trip with positive/negative cases, and the client
against mocked fetch. Wired into the root build script.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Product requirements for @logicsrc/agentstack: summary, goals/non-goals,
users, current v0.1.0 state, functional/non-functional requirements,
roadmap (M1–M6), success metrics, and open questions. Grounded in the
existing in-memory coordinator and the agentstack OpenSpec capability.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
AgentGit is a thin, DID-gated source-collaboration layer over a backend
forge (default Forgejo at git.profullstack.com, BBS-members-only) — not a
new git host. M1 implements the contract and engines:
- forge/adapter.ts: ForgeAdapter interface (only forge-specific surface)
- forge/forgejo.ts: ForgejoAdapter over Forgejo/Gitea REST v1 (injectable
fetch, typed errors), incl. ensureUser for member provisioning
- access.ts: gateAccess DID membership gate (owner/role/visibility)
- merge-policy.ts: evaluateMergePolicy pure engine (reviews, reputation
floor, checks, escrow, merge method, agent-merge toggle)
- service.ts: AgentGitService ties gate + policy to the adapter; refuses
policy-failing merges; provisionMember hook for AgentBBS
- schemas: logicsrc-repo + logicsrc-pull-request, registered in
@logicsrc/validators with fixtures
- docs/agentgit.md spec; plugin wired into root build (default/disabled)
27 vitest tests pass; full monorepo build green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>