Commit graph

18 commits

Author SHA1 Message Date
b1805d08e5
Add SSH keys and config to credential sharing (#139)
Some checks are pending
CI / build (push) Waiting to run
test / test (push) Waiting to run
* 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>
2026-08-13 17:03:00 -07:00
RissRIce
1cfec322ac
fix(web): validate ontology pagination (#138)
Some checks failed
CI / build (push) Has been cancelled
test / test (push) Has been cancelled
2026-08-10 18:59:16 -07:00
4c88155f08
test(web): fail the build when marketing copy drifts from shipped providers (#126)
Some checks are pending
CI / build (push) Waiting to run
test / test (push) Waiting to run
The last three PRs all fixed the same class of bug. /credential-sharing
and README.md are hand-written copy; the providers they advertise are a
real registry in @logicsrc/plugin-credential-sharing. Nothing connected
the two, so the `team` provider shipped on 2026-07-13 and three weeks
later both surfaces still described a five-provider tool with no mention
of teams. The docs were right the whole time -- only the pages people
actually land on had gone stale, which is worse, because it reads as
"the product cannot do this" rather than as a documentation gap.

Assert it instead. For every provider in the registry, the Credential
Sharing section and the README must say something that counts as
advertising it. The registry's own `name` cannot be the proof -- `env`
is "Local .env file" and `team` is "LogicSRC Team Vault", neither of
which is how the copy reads -- so each provider declares its own
pattern, and a provider with no declaration fails too. That way adding
a provider forces a deliberate answer about the customer-facing copy.

Verified against the bug it is meant to catch: reverting the team copy
reproduces "These providers ship but /credential-sharing never mentions
them: team", and reverting the README line reproduces the same for
sh1pt and team.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-03 07:18:13 -07:00
f41abbd712 feat(web): put the one-line install in front of everyone
The CLI is the product, and the way you get it was nowhere on the site. You
had to already know the URL of a script served out of public/.

Two placements, one command:

- The homepage hero gets the loud version, directly under the lede and above
  the fold -- a bordered dark panel, the command at full size, Copy alongside.
- Every page carries a compact version in the rail, between the brand and the
  nav. Present on arrival, never competing with navigation.

Both come from renderInstallCommand() in one module. The homepage builds its
HTML as a string and the rest of the site is JSX, which is precisely the shape
that lets one copy of a command drift while the other stays right -- so there
is one definition and SiteShell renders it rather than restating it.

The command keeps its flags: `curl -fsSL`. Without -f, curl prints an HTTP
error body and still exits 0, so a 404 gets piped into sh; without -L the
install breaks the first time the URL redirects. This is the form install.sh
already documents in its own header.

Copy is one delegated listener on document for any [data-copy] button, mounted
site-wide in the layout. Delegation because the two placements arrive by
different rendering paths and a document listener does not care which; it also
means the next copy button needs the attribute and no wiring. It falls back to
a throwaway textarea + execCommand outside a secure context, where
navigator.clipboard is simply undefined, so the button never no-ops silently.

The contract tests pin the command, both placements, and that the clipboard
payload equals the visible text -- a Copy button that hands over something
other than what is on screen is worse than no button. They also read
public/install.sh and assert it is #!/bin/sh and documents this exact command,
so `| sh` cannot quietly become a lie.

apps/logicsrc-web: 13 new tests pass, 46 total. The ontology-api contract file
fails to resolve @logicsrc/validators, which it also does on a pristine
origin/master -- unbuilt workspace package, unrelated to this change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 12:55:30 -07:00
3649f78fb6
feat(web): serve the CLI login flow from the apex (#106)
`logicsrc login` defaults to https://logicsrc.com (#104), but every path it
needs returns 404 there: the apex runs the marketing app, while /cli/* lives in
apps/pwa on its own service.

Proxy those paths from the app that owns the apex, the same way CommandBoard is
already proxied. No DNS record, no Railway custom domain, and no subdomain --
and it makes the CLI's existing default origin correct rather than requiring
another change to chase it.

Pointing the apex at the pwa instead was the obvious alternative and is wrong:
the pwa serves `/` too, so it would take the marketing site down with it.

Proxied:
  /cli/:path*            the device-code and loopback login flows
  /api/me                identity
  /api/credshare/:path*  the credential-sharing API used after login
  /auth/:path*           /cli/authorize and /cli/device are behind requireAuth,
                         so an unauthenticated visitor is redirected here; without
                         it the browser half of the flow dead-ends on a 404

Order matters and is asserted: CommandBoard owns a catch-all /api/:path*, so
/api/me and /api/credshare/* have to match first or CLI auth silently goes to
the wrong service.

Rewrite construction is factored into pure functions so the ordering is testable
without booting Next, and degrades cleanly: with CREDENTIALS_APP_URL unset the
output is byte-identical to what shipped before.

Requires CREDENTIALS_APP_URL on the logicsrc-web service, pointing at the
credentials app's origin.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-30 10:25:16 -07:00
eaf0a6162b
feat(web): move Hire Us pricing to $400/hour metered billing (PRD 0002) (#102)
* 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>
2026-07-28 10:34:49 -07:00
da5f6f8381
feat(openontology): Phase 2 + Phase 3 — storage, REST/SSE, MCP, RDF/SHACL, adapters, TUI, explorer (#101)
Some checks are pending
CI / build (push) Waiting to run
test / test (push) Waiting to run
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>
2026-07-28 05:10:33 -07:00
phucnguyen1707
59013cf0af
Sanitize rendered web content (#77) 2026-06-15 01:34:44 -07:00
phucnguyen1707
14fe9d608f
Require CoinPay userinfo identity (#21) 2026-06-13 23:01:15 -07:00
Autowebassat-blip
e89c10cc80
Reject malformed CoinPay webhook signatures (#41)
* Reject malformed CoinPay webhook signatures

* Fix webhook signature type guard

---------

Co-authored-by: Codex Microtask Operator <codex-microtask@example.com>
2026-06-13 22:56:12 -07:00
Autowebassat-blip
055cc09d14
Fallback invalid CoinPay checkout amounts (#42)
Co-authored-by: Codex Microtask Operator <codex-microtask@example.com>
2026-06-13 22:55:53 -07:00
Autowebassat-blip
9ed98ceaf7
Accept spaced CoinPay signature parts (#17) 2026-06-11 21:04:41 -07:00
Autowebassat-blip
d558554da3
Reject extra session token segments (#18) 2026-06-11 21:04:25 -07:00
f0a9f3890e Migrate logicsrc-web from Vite SPA to Next.js 16 App Router
Replaces the Vite single-page app + custom Node server.js with a Next.js
16.2.6 App Router app.

- proxy.ts (src/proxy.ts): www.logicsrc.com -> logicsrc.com 301 over https,
  preserving path + query (the original request, now via Next 16 Proxy).
- One SSR page via an optional catch-all ([[...slug]]) that renders the same
  marketing/spec page for each known top-level route (/docs, /blog, /openspec,
  ...) and 404s unknown paths, preserving existing canonical URLs. Markup is a
  faithful server-rendered port of the old main.ts (SEO upgrade over the prior
  client render); interactivity (hire-us form, CoinPay button, section scroll)
  moves to a client component.
- API routes ported to app/api/**: hire-us coinpay-checkout + project-request,
  oauth/coinpay start/callback/session, webhooks/coinpay. Shared logic in
  src/lib/coinpay.ts (eligibility, payment-rail selection, webhook verify,
  HMAC session sign/verify, cookies).
- commandboard-api (/health + /api/boards|tasks|plugins/*) is no longer mounted
  in-process; next.config.ts proxies those paths to COMMANDBOARD_API_URL via
  afterFiles rewrites (our own /api routes match first).
- Build/start switch to next build / next start. Contract tests rewritten to
  exercise proxy.ts, the route handlers, and pure helpers directly (21 passing);
  Playwright webServer updated.

Deployment (Railway): set COMMANDBOARD_API_URL to the commandboard-api service
URL and run it as its own service; root start now runs next start.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-07 03:41:48 +00:00
b1d8fe475a feat: add c0mpute plugin and hire-us request flow 2026-06-06 19:42:00 +00:00
a0314eb774 Add credential sharing openspec 2026-06-06 18:47:41 +00:00
de517ba9b7 Add LogicSRC hire us page 2026-06-06 17:35:22 +00:00
8e6124d23a Add LogicSRC web contract and E2E tests 2026-06-06 16:59:29 +00:00