+8 Gitea, +6 GitHub — both sides had work the other lacked. Merged so both survive.
An independent reviewer was pointed at this branch with one instruction: find what was
dropped. Verdict: 0 dropped behaviours, 17 confirmed survived.
The conflicts were settled against real contracts, not by preference
The four src/clients/*.ts files were the crux — both sides rewrote them independently.
Rather than pick a side, the resolver read the target services' actual contracts:
st0re-client.ts — Gitea POSTed to /v1/st0re/artifacts. Reading sax3l/st0re src/api/routes/artifacts.ts shows that path is registered GET-only; the register
endpoint is /v1/st0re/artifacts/register, which is GitHub's. Gitea's client would
never have registered anything. GitHub's endpoint wins on evidence. Gitea's URL
normalisation, response-body-in-error and required retention_policy/data_residency
all kept.
n0d-client.ts — the two sides disagreed on state vs status and on the enum. sax3l/n0dpackages/schemas/n0d.job.schema.json requires status with a 13-state
enum and carries the note "Do not add 'succeeded' — that is 'completed' under a
different name." GitHub's field name was right, Gitea's vocabulary was wrong. Both
historical type names kept as aliases so no import breaks.
cl0ud-client.ts — kept Gitea's fail-closed normalisation
(body.decision ?? "deny"). GitHub returned res.json() raw, so a 200 with a missing decision field would have read as permitted. This is the security-relevant half.
What the reviewer confirmed survived
GitHub's 17 audit bug-fixes (byte-identical), the CI quality gate, the .gitignore
blanket-.env fix and its !.env.example negation, the added platform clients and
registry services — plus Gitea's Iceberg write-boundary work, its Zod parseBody
gates on all four lakehouse routes, its Zitadel/AUTH_ENFORCE env block, and its X-Tenant-Id/X-Actor-Id threading.
Measured
Gitea main
GitHub main
Merged
pnpm install
ok
FAILS (packages field missing)
ok — Gitea's workspace fix carries over
pnpm test
121 passed / 1 skip
88 passed
121 passed / 1 skip
typecheck (scoped)
exit 0
—
exit 0
whole-repo tsc
9 errors
0
the same 9, line numbers shifted only
GitHub's 88 tests are a strict subset of Gitea's file set, so no GitHub-only test was
lost. The 1 skip is honest: iceberg-write-service.realinfra.test.ts skips because
ST0RE returned 401, and says so rather than fabricating a pass.
Two things left unsettled ON PURPOSE
packages/schemas/0pt.{lineage_event,signal}.schema.json — the two lines disagree on
field vocabulary (producer object vs flat string; entity_id/observed_at/ confidence_level vs entity_key/detected_at/numeric confidence). Nothing in the
repo reads, writes or validates against either file, so the code cannot decide it. Both
vocabularies are retained as optional properties with the disagreement recorded in each
schema's own description. Whoever builds signal detection picks one pair and deletes
the other.
Also: typecheck is scoped to src/+tests/ as blocking and whole-repo as advisory.
The 9 whole-repo errors are all Wave-2 Zod schemas typing a field as string where the
service wants a narrower union — tightening them changes which request bodies the routes
accept, which is a behaviour change that does not belong in a merge.
One review finding, fixed
The tree asserted the banned scraper stack was "excluded by design" while all 64 files of services/scrapers/ are present. The README now states the policy and the state, and
points at PR #1, which exists to delete them.
Deleting them here would fold a policy change into a history merge.
Backup:backup/pre-github-unify-2026-07-28.
**+8 Gitea, +6 GitHub — both sides had work the other lacked.** Merged so both survive.
An independent reviewer was pointed at this branch with one instruction: find what was
dropped. Verdict: **0 dropped behaviours, 17 confirmed survived.**
### The conflicts were settled against real contracts, not by preference
The four `src/clients/*.ts` files were the crux — both sides rewrote them independently.
Rather than pick a side, the resolver read the target services' actual contracts:
- **`st0re-client.ts`** — Gitea POSTed to `/v1/st0re/artifacts`. Reading `sax3l/st0re`
`src/api/routes/artifacts.ts` shows that path is registered **GET-only**; the register
endpoint is `/v1/st0re/artifacts/register`, which is GitHub's. **Gitea's client would
never have registered anything.** GitHub's endpoint wins on evidence. Gitea's URL
normalisation, response-body-in-error and required `retention_policy`/`data_residency`
all kept.
- **`n0d-client.ts`** — the two sides disagreed on `state` vs `status` and on the enum.
`sax3l/n0d` `packages/schemas/n0d.job.schema.json` requires `status` with a 13-state
enum and carries the note *"Do not add 'succeeded' — that is 'completed' under a
different name."* GitHub's field name was right, Gitea's vocabulary was wrong. Both
historical type names kept as aliases so no import breaks.
- **`cl0ud-client.ts`** — kept **Gitea's fail-closed normalisation**
(`body.decision ?? "deny"`). GitHub returned `res.json()` raw, so a 200 with a missing
`decision` field would have read as permitted. This is the security-relevant half.
### What the reviewer confirmed survived
GitHub's 17 audit bug-fixes (byte-identical), the CI quality gate, the `.gitignore`
blanket-`.env` fix **and** its `!.env.example` negation, the added platform clients and
registry services — **plus** Gitea's Iceberg write-boundary work, its Zod `parseBody`
gates on all four lakehouse routes, its Zitadel/`AUTH_ENFORCE` env block, and its
`X-Tenant-Id`/`X-Actor-Id` threading.
### Measured
| | Gitea main | GitHub main | Merged |
|---|---|---|---|
| `pnpm install` | ok | **FAILS** (`packages field missing`) | ok — Gitea's workspace fix carries over |
| `pnpm test` | 121 passed / 1 skip | 88 passed | **121 passed / 1 skip** |
| `typecheck` (scoped) | exit 0 | — | **exit 0** |
| whole-repo tsc | 9 errors | 0 | **the same 9**, line numbers shifted only |
GitHub's 88 tests are a strict subset of Gitea's file set, so no GitHub-only test was
lost. The 1 skip is honest: `iceberg-write-service.realinfra.test.ts` skips because
ST0RE returned 401, and says so rather than fabricating a pass.
### Two things left unsettled ON PURPOSE
`packages/schemas/0pt.{lineage_event,signal}.schema.json` — the two lines disagree on
field vocabulary (`producer` object vs flat string; `entity_id`/`observed_at`/
`confidence_level` vs `entity_key`/`detected_at`/numeric `confidence`). **Nothing in the
repo reads, writes or validates against either file**, so the code cannot decide it. Both
vocabularies are retained as optional properties with the disagreement recorded in each
schema's own `description`. Whoever builds signal detection picks one pair and deletes
the other.
Also: `typecheck` is scoped to `src/`+`tests/` as blocking and whole-repo as advisory.
The 9 whole-repo errors are all Wave-2 Zod schemas typing a field as `string` where the
service wants a narrower union — tightening them changes which request bodies the routes
accept, which is a behaviour change that does not belong in a merge.
### One review finding, fixed
The tree asserted the banned scraper stack was "excluded by design" while all 64 files of
`services/scrapers/` are present. The README now states the policy **and** the state, and
points at [PR #1](https://git.siax.io/sax3l/0pt/pulls/1), which exists to delete them.
Deleting them here would fold a policy change into a history merge.
**Backup:** `backup/pre-github-unify-2026-07-28`.
The 0pt data-engine build has been broken on main since 2026-07-08 (commit
c897311). document-capture-service.ts, document-extraction-service.ts,
source-query-service.ts, external-writeback-service.ts (and, more recently,
dataset-iceberg-mapping-service.ts, iceberg-write-service.ts,
source-rate-policy-service.ts) all import six platform client modules and two
registry services that were never committed: src/clients/{aud0,cl0ud,n0d,
st0re}-client.ts, src/services/{dataset,source}-service.ts, plus
src/types/{dataset,source}.ts. `next build` failed with 17 "Module not found"
errors, and every route under app/api/0pt/* returned HTTP 500 in dev with the
Turbopack compile error leaked straight into the JSON response body.
This adds the missing files, following the same fetch-based client shape
already established by src/clients/st0re-lakehouse-client.ts (thin interface,
HTTP implementation, deterministic in-memory mock used when no base URL is
configured) and the same "pure core, thin in-memory registry, AUD0-evented
mutation" shape the existing SourceRatePolicyService/DatasetIcebergMappingService
already use for DatasetService/SourceService. Each new client mock is
documented as a mock/degrade path, never a fabricated success, per this
repo's CLAUDE.md honesty doctrine.
Also wires up "typecheck" and "test" npm scripts (tsc --noEmit / vitest run)
and adds vitest as a devDependency -- previously `pnpm test` failed
immediately with ERR_PNPM_NO_SCRIPT even though 13 test files (~89 cases)
already existed under tests/p0/.
Verified: `pnpm run typecheck` clean, `pnpm run test` 88 passed / 1 todo
(pre-existing live-ST0RE-only skip), `pnpm run build` exits 0 (39/39 routes
compiled, only the pre-existing intentional optional `mssql` import warning
remains). Re-verified live on port 4097: GET /api/0pt/sources/rate-policies,
GET /api/0pt/lakehouse/mappings, POST /api/0pt/documents/capture, GET
/api/0pt/documents/[id]/extraction, and both GET/POST
/api/0pt/sources/[id]/query now return real application responses (200/400/405)
instead of HTTP 500 with a leaked compile error.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
.env.example was referenced by README.md and required by docs/09-build-plan.md
(Phase 0) but never existed in the repo. Root cause: .gitignore's `.env*` rule
silently ignored it too, so `git add .env.example` was always a no-op even if
someone created it locally.
- Add .gitignore exception: `!.env.example`.
- Add .env.example covering every process.env.* read found in this pass
(grep-verified across app/, lib/, src/), organized by concern, each section
marked "wired in code today" or "documented target, not yet read by code"
per docs/08-infrastructure.md. No real values; matches repo convention.
Excluded ODOO_BASE_URL: only appears inside a comment describing a past
refactor away from module-level env reads, not an actual process.env read.
pnpm test: 88 passed, 1 todo, 0 failures (13 files, tests/**/*.test.ts).
tsc --noEmit: clean.
* docs: full accuracy overhaul, grounded claim-by-claim in real code
Every doc in this repo (CLAUDE.md, README.md, SECURITY.md, docs/*.md) was
audited and rewritten by a multi-pass process: independent rewrite per
cluster, adversarial re-verification of that rewrite against the actual
code, then a final cross-doc consistency pass — each stage re-checking
claims against the repo directly rather than trusting the previous stage.
Two corrections matter most:
1. docs/ICEBERG_WRITE_BOUNDARY.md, docs/11-lakehouse-write-plan.md, and
docs/P0_QUERY_SOURCE_FLOW.md asserted src/services/quality-check-service.ts,
quality-report-service.ts, src/data-platform/services/catalog-service.ts,
and sql/002_p0_dataset_quality.sql/sql/003_*.sql as real, tested code.
git log --all across every branch shows zero commits for any of these
paths, ever. 0pt's data-quality/QA enforcement does not exist in code.
Corrected everywhere, with a new docs/P0_DATASET_QUALITY_FLOW.md added
as an explicit target-spec (not-yet-built) replacement.
2. github.com/sax3l/0pt (this repo) and git.siax.io/sax3l/0pt (Gitea) are
two diverged remotes sharing part of the same history (services/api/
workers/adaptive_router.py is byte-identical between them). Gitea's main
independently accumulated a WAF-bypass/proxy-rotation scraper stack in
three 2026-07-13 commits, since removed via a Gitea PR; GitHub's main
never had it. Which remote is canonical/deployed is unresolved — flagged
consistently across the corpus, not decided here.
Also newly and consistently documented: the tenant_id/project_id-read-from-
client-body gap on the P0 lakehouse/rate-policy/source-query routes (no
auth/session system exists anywhere in this repo yet); that every CL0UD/
N0D/AUD0/ST0RE platform client is real but mock-only (zero process.env
reads for any of their *_URL vars); that two P0 mutation routes
(lakehouse/mappings, sources/rate-policies) always fail over HTTP today
because their prerequisite entity can never be created via any route; a
schema bug in sql/004 (FK references to opt_datasets/opt_sources, which
don't exist); and that 0pt.app's live deployment was independently
confirmed (curled this pass) to be running this exact codebase with
DATABASE_URL/REDIS_URL unset in production.
New file: docs/12-edge-capability-api.md — the edge-native P0 TypeScript
capability contract (app/api/0pt/*, /api/screen, /api/ai/parse), previously
undocumented and distinct from docs/04-api.md's FastAPI-engine-only scope.
Verified before commit: tsc --noEmit clean; vitest run 88 passed, 1 todo,
0 failures (13 files) — docs-only change, no source touched.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* docs: reflect PR #5's lakehouse-mapping-registry + status-code fixes
Follow-up to keep docs/12-edge-capability-api.md accurate once both PRs
land: fix/0pt-full-bug-hunt (#5) fixed the shared-instance bug in the
lakehouse mapping routes and added proper status-code mapping to the
document extraction route. This doc was written against the pre-fix
state in the same pass that added it, so it needs the same correction.
Does not claim the lakehouse write path fully works now -- the deeper
dataset-registration gap (no route ever calls DatasetService.registerDataset)
is unchanged and still blocks it end-to-end; only the shared-instance half
of the bug is fixed.
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* docs: correct stale quality-check-service.ts comment references
Two source comments still cited src/services/quality-check-service.ts as an
existing precedent. It doesn't exist anywhere in this repo's history (git
log --all: zero commits, any branch, ever) -- confirmed during the earlier
full-doc-corpus accuracy pass. Points each comment at the real precedent
instead (document-extraction-service.ts / iceberg-write-service.ts).
Applied from task_05ec36aa, spun off during that pass.
* fix: 17 confirmed bugs from full-repo audit (edge + Python engine)
Found by an 8-finder bug hunt across app/api, src/services, src/clients,
frontend, the Python engine, the data layer, config/infra, and tests, then
independently adversarially verified per finding (32 raw findings -> 27
confirmed real bugs; 5 refuted). This commit applies the 17 confirmed as
safe, mechanical fixes -- no architecture/design decision required. The
other 10 need a decision and are reported separately, not touched here.
Most consequential, app/api/payments/[order_id]/deposit/route.ts:
the client-supplied Origin request header was trusted unsanitized both as
a server-side fetch target (SSRF) and as Stripe Checkout success/cancel
redirect URLs (open redirect tied to a real payment flow), and a failed
order lookup silently fell through to a hardcoded deposit amount instead
of 404. Fixed by reading the order directly via lib/orders-store.ts
(mirroring the sibling release/route.ts, which already did this correctly)
instead of both trusting the Origin header and self-issuing an HTTP fetch.
Also fixed:
- 5 routes (orders, orders/[id], estimates, subscriptions, ai/parse) that
500'd instead of 400ing on a literal JSON `null` body, since req.json()
resolves (not rejects) for that input and bypasses the try/catch guard.
- app/api/0pt/lakehouse/mappings/{,[mappingId]/{,writes/}}route.ts each
instantiated their own private service instances, so a mapping created
via POST was invisible to the sibling GET and writes routes even before
the separately-tracked dataset-registration gap. Extracted a shared
singleton module, src/services/lakehouse-mapping-registry.ts.
- app/api/0pt/documents/[documentId]/extraction/route.ts collapsed every
error (not-configured, policy-denied, policy-approval-required,
not-found) into a blanket 400; now status-mapped like the sibling
sources/[sourceId]/query route.
- app/api/vehicles/route.ts: plate query param wasn't URL-encoded before
being concatenated into the outbound RDW request, allowing extra query
parameters to be injected; added a strict allowlist + encodeURIComponent.
- app/api/uploads/route.ts: POST returned a fake 200 success with
put_url: null even when "configured", instead of continuing to report
501 until presign generation is actually implemented.
- src/adapters/document-intelligence-adapter.ts: the result-polling loop
never checked response.ok, so a non-2xx poll response (auth expiry,
429, 5xx) was silently retried until a generic timeout masked the real
error.
- app/order/[id]/page.tsx: finish() never cleared the production-simulation
setInterval, so it kept firing (and re-rendering) forever after delivery.
- app/admin/page.tsx: the metrics fetch had no response/shape validation
(unlike the adjacent orders fetch), rendering literal "undefined" stat
tiles on a malformed response instead of falling back to <ErrorState/>.
- services/api/workers/pipeline.py: `config.get("seeds") or DEMO_SEEDS`
used truthy-or, so an explicit empty seeds dict was silently replaced
with hardcoded demo org numbers instead of being respected as empty.
- services/api/workers/resolve.py: corroboration (confidence/agreed_sources
bump) no longer fires for two hits from the same source under fuzzy
match -- only a genuinely independent source now counts, per docs/06's
own ">=2 independent sources agree" definition -- and a merged record's
provenance array now actually includes the corroborating source's
fields instead of only a bare source-name string.
- services/api/orchestrator/main.py: create_order's int(...) on
unvalidated client volume/confidence fields raised on any
decimal-formatted input (int("92.5") -> ValueError); now coerced via
float() first with a safe default. /vehicles/{plate} and /screen also
now catch connector exceptions and degrade gracefully, matching
pipeline.py's existing pattern for the same connectors.
- lib/orders-store.ts: postgres.js (pinned v3.4.9) returns bigint/numeric
columns as strings by default; getOrder/listOrders/setStatus were
silently handing callers stringified money fields. Added a narrow
per-row coercion instead of touching the shared client's global type
map (which would affect unrelated tables' bigint/numeric columns too).
- lib/subscriptions-store.ts: the in-memory fallback returned
oldest-first while the Postgres branch explicitly orders newest-first;
now consistent, mirroring the identical fix already in orders-store.ts.
- app/api/auth/login/route.ts: DEV_LOGIN_KEY was compared with a plain
!==, a non-constant-time comparison; replaced with crypto.timingSafeEqual.
Verified before commit: tsc --noEmit clean; vitest run 88 passed, 1 todo,
0 failures (13 files, unchanged from before this commit -- no existing
test's assertions needed to change). services/api/workers/resolve.py's
source-novelty and provenance-merge logic additionally verified by direct
execution against synthetic same-source/cross-source inputs (no Python
test suite exists in this repo to add to). services/api/workers/pipeline.py
and services/api/orchestrator/main.py verified with python -m py_compile
(no httpx/fastapi installed in this environment to run them directly).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
* fix(engine): treat explicit seeds:null as missing to avoid 500 in run_job
Four-eyes follow-up to this PR's own seeds change. Switching from
`config.get('seeds') or DEMO_SEEDS` to `config['seeds'] if 'seeds' in config`
correctly stopped an explicit empty {} from being backfilled with DEMO_SEEDS,
but regressed on client-controlled `{"seeds": null}`: seeds became None and
the downstream `seeds.get(market, [])` raised AttributeError -> 500. That path
is reachable (POST /orders stores raw body.config; POST /orders/{id}/run passes
it straight to run_job). Using `config.get('seeds') is not None` keeps the
intended behaviour (explicit {} honoured, never silently seeded with demo data)
while restoring the None-tolerance the old idiom had.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
* ci: add typecheck/lint/test quality gate (was deploying unguarded)
* ci: make quality gate functional — typecheck + test, drop unwired lint
The lint step ran `pnpm run lint` = `eslint .`, but eslint is not a dependency
of this repo (absent from pnpm-lock.yaml), so the step failed on command-not-found
and, since a failed step aborts the job, prevented typecheck from ever running —
a red, useless gate on every PR. Replace it with the vitest suite (`pnpm run test`,
88 passing) so the gate matches the PR's stated 'typecheck + test' intent and is
green on a clean tree. Typecheck now uses `pnpm run typecheck` (carries the
`-p tsconfig.json` the script defines). Wiring eslint is left as a separate decision.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Merges github/main (6 commits: 17-bug audit fix, CI quality gate,
.env.example + .gitignore fix, missing platform clients + registry
services, docs accuracy overhaul) into gitea/main (8 commits: Iceberg
write boundary + live ST0RE verification, lakehouse mapping routes,
Waves 1-4 foundational hardening).
16 conflicts. Both sides had independently written the four
src/clients/*.ts and the two registry services, so those are combined
rather than picked:
- st0re-client.ts: GitHub's endpoint wins on evidence. POST goes to
/v1/st0re/artifacts/register, not /v1/st0re/artifacts -- verified in
sax3l/st0re src/api/routes/artifacts.ts, where the bare path is a
GET-only list route. Gitea's URL normalisation, error-body text and
mock kept.
- n0d-client.ts: field is `status`, not `state`, with N0D's real 13-state
union -- verified in sax3l/n0d packages/schemas/n0d.job.schema.json,
which says explicitly not to add "succeeded". Gitea's mock echo,
URL normalisation and error-body text kept.
- cl0ud-client.ts: Gitea's fail-closed HTTP normalisation kept (a 200
with no `decision` reads as "deny", not as a silent allow). GitHub's
PolicyDecision/PolicyDecisionValue naming kept; PolicyResult retained
as an alias.
- aud0-client.ts: permissive union of both input types (optional
payload/metadata/correlation_id), GitHub's echo return, Gitea's URL
normalisation and error-body text, plus a JSON-parse fallback so a
successful POST with an empty body does not throw.
- dataset-service.ts / source-service.ts: GitHub's record/DTO split and
event naming (0pt.source_created / 0pt.create_source, which the merged
docs already reference) plus Gitea's name validation, description /
created_by fields and actor_id derivation. List filters are the union.
- lakehouse mapping routes: GitHub's shared lakehouse-mapping-registry
singletons (without which every write 400'd "mapping not found") AND
Gitea's Zod parseBody validation. Both, not either.
- src/types/source.ts, src/types/dataset.ts: unions of both vocabularies;
GitHub's `access` legal-sourcing field kept.
- .env.example: GitHub's audited inventory plus Gitea's Zitadel /
AUTH_ENFORCE block.
- CLAUDE.md, docs/ICEBERG_WRITE_BOUNDARY.md: GitHub's corrections kept,
Gitea's live-verification kept and marked as superseding the
"would 400 outright" finding (Gitea had already added the required
X-Tenant-Id/X-Actor-Id headers).
- pnpm-lock.yaml: regenerated with pnpm 9.15.9, not merged.
Also corrects CLAUDE.md's claim that the banned services/scrapers stack
was "since removed" -- the removal PR is open, not merged, and this
branch inherits that code from Gitea main. Nothing here resurrects,
extends or re-wires any of it; services/scrapers/ is byte-identical to
Gitea main.
Verified on the merged tree: typecheck (src/ + tests/) clean;
17 test files, 121 passed, 1 skipped (the realinfra test skipping
honestly on ST0RE HTTP 401), 0 failures. Gitea main baseline was the
same 121/1; GitHub main baseline could not even `pnpm install`
("packages field missing or empty") -- Gitea's pnpm-workspace.yaml fix
resolves that. Whole-repo `typecheck:app` reports 9 errors, an identical
set to Gitea main's baseline, so it is advisory in both CI workflows
rather than deleted. Lint: 16 problems, identical to Gitea baseline.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An independent review of this unification branch flagged that the tree asserts the
bypass/stealth/proxy-rotation paths are 'excluded by design' while
services/scrapers/ still contains all 64 files implementing exactly that --
curl_cffi TLS impersonation, cloudscraper, Tor rotation, WAFBypass, ProxyRotator,
plus committed .pyc bytecode.
Both statements were true in their own frame: CLAUDE.md §1 is the policy, and the
code is the state. The README stated the policy in the present tense, which reads
as a description of the tree.
This adds the state next to the policy and points at PR #1, which exists to delete
the directory and has been open since 2026-07-19. docs/06-data-engine.md already
documented the arrival honestly; this brings the README in line with it.
Deliberately NOT deleting services/scrapers/ here. That is PR #1's job, and folding
a policy deletion into a history-unification merge would make both harder to
review. Recording the truth is what this merge owes; removing the code is a
separate, reviewable change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
+8 Gitea, +6 GitHub — both sides had work the other lacked. Merged so both survive.
An independent reviewer was pointed at this branch with one instruction: find what was
dropped. Verdict: 0 dropped behaviours, 17 confirmed survived.
The conflicts were settled against real contracts, not by preference
The four
src/clients/*.tsfiles were the crux — both sides rewrote them independently.Rather than pick a side, the resolver read the target services' actual contracts:
st0re-client.ts— Gitea POSTed to/v1/st0re/artifacts. Readingsax3l/st0resrc/api/routes/artifacts.tsshows that path is registered GET-only; the registerendpoint is
/v1/st0re/artifacts/register, which is GitHub's. Gitea's client wouldnever have registered anything. GitHub's endpoint wins on evidence. Gitea's URL
normalisation, response-body-in-error and required
retention_policy/data_residencyall kept.
n0d-client.ts— the two sides disagreed onstatevsstatusand on the enum.sax3l/n0dpackages/schemas/n0d.job.schema.jsonrequiresstatuswith a 13-stateenum and carries the note "Do not add 'succeeded' — that is 'completed' under a
different name." GitHub's field name was right, Gitea's vocabulary was wrong. Both
historical type names kept as aliases so no import breaks.
cl0ud-client.ts— kept Gitea's fail-closed normalisation(
body.decision ?? "deny"). GitHub returnedres.json()raw, so a 200 with a missingdecisionfield would have read as permitted. This is the security-relevant half.What the reviewer confirmed survived
GitHub's 17 audit bug-fixes (byte-identical), the CI quality gate, the
.gitignoreblanket-
.envfix and its!.env.examplenegation, the added platform clients andregistry services — plus Gitea's Iceberg write-boundary work, its Zod
parseBodygates on all four lakehouse routes, its Zitadel/
AUTH_ENFORCEenv block, and itsX-Tenant-Id/X-Actor-Idthreading.Measured
pnpm installpackages field missing)pnpm testtypecheck(scoped)GitHub's 88 tests are a strict subset of Gitea's file set, so no GitHub-only test was
lost. The 1 skip is honest:
iceberg-write-service.realinfra.test.tsskips becauseST0RE returned 401, and says so rather than fabricating a pass.
Two things left unsettled ON PURPOSE
packages/schemas/0pt.{lineage_event,signal}.schema.json— the two lines disagree onfield vocabulary (
producerobject vs flat string;entity_id/observed_at/confidence_levelvsentity_key/detected_at/numericconfidence). Nothing in therepo reads, writes or validates against either file, so the code cannot decide it. Both
vocabularies are retained as optional properties with the disagreement recorded in each
schema's own
description. Whoever builds signal detection picks one pair and deletesthe other.
Also:
typecheckis scoped tosrc/+tests/as blocking and whole-repo as advisory.The 9 whole-repo errors are all Wave-2 Zod schemas typing a field as
stringwhere theservice wants a narrower union — tightening them changes which request bodies the routes
accept, which is a behaviour change that does not belong in a merge.
One review finding, fixed
The tree asserted the banned scraper stack was "excluded by design" while all 64 files of
services/scrapers/are present. The README now states the policy and the state, andpoints at PR #1, which exists to delete them.
Deleting them here would fold a policy change into a history merge.
Backup:
backup/pre-github-unify-2026-07-28.* docs: correct stale quality-check-service.ts comment references Two source comments still cited src/services/quality-check-service.ts as an existing precedent. It doesn't exist anywhere in this repo's history (git log --all: zero commits, any branch, ever) -- confirmed during the earlier full-doc-corpus accuracy pass. Points each comment at the real precedent instead (document-extraction-service.ts / iceberg-write-service.ts). Applied from task_05ec36aa, spun off during that pass. * fix: 17 confirmed bugs from full-repo audit (edge + Python engine) Found by an 8-finder bug hunt across app/api, src/services, src/clients, frontend, the Python engine, the data layer, config/infra, and tests, then independently adversarially verified per finding (32 raw findings -> 27 confirmed real bugs; 5 refuted). This commit applies the 17 confirmed as safe, mechanical fixes -- no architecture/design decision required. The other 10 need a decision and are reported separately, not touched here. Most consequential, app/api/payments/[order_id]/deposit/route.ts: the client-supplied Origin request header was trusted unsanitized both as a server-side fetch target (SSRF) and as Stripe Checkout success/cancel redirect URLs (open redirect tied to a real payment flow), and a failed order lookup silently fell through to a hardcoded deposit amount instead of 404. Fixed by reading the order directly via lib/orders-store.ts (mirroring the sibling release/route.ts, which already did this correctly) instead of both trusting the Origin header and self-issuing an HTTP fetch. Also fixed: - 5 routes (orders, orders/[id], estimates, subscriptions, ai/parse) that 500'd instead of 400ing on a literal JSON `null` body, since req.json() resolves (not rejects) for that input and bypasses the try/catch guard. - app/api/0pt/lakehouse/mappings/{,[mappingId]/{,writes/}}route.ts each instantiated their own private service instances, so a mapping created via POST was invisible to the sibling GET and writes routes even before the separately-tracked dataset-registration gap. Extracted a shared singleton module, src/services/lakehouse-mapping-registry.ts. - app/api/0pt/documents/[documentId]/extraction/route.ts collapsed every error (not-configured, policy-denied, policy-approval-required, not-found) into a blanket 400; now status-mapped like the sibling sources/[sourceId]/query route. - app/api/vehicles/route.ts: plate query param wasn't URL-encoded before being concatenated into the outbound RDW request, allowing extra query parameters to be injected; added a strict allowlist + encodeURIComponent. - app/api/uploads/route.ts: POST returned a fake 200 success with put_url: null even when "configured", instead of continuing to report 501 until presign generation is actually implemented. - src/adapters/document-intelligence-adapter.ts: the result-polling loop never checked response.ok, so a non-2xx poll response (auth expiry, 429, 5xx) was silently retried until a generic timeout masked the real error. - app/order/[id]/page.tsx: finish() never cleared the production-simulation setInterval, so it kept firing (and re-rendering) forever after delivery. - app/admin/page.tsx: the metrics fetch had no response/shape validation (unlike the adjacent orders fetch), rendering literal "undefined" stat tiles on a malformed response instead of falling back to <ErrorState/>. - services/api/workers/pipeline.py: `config.get("seeds") or DEMO_SEEDS` used truthy-or, so an explicit empty seeds dict was silently replaced with hardcoded demo org numbers instead of being respected as empty. - services/api/workers/resolve.py: corroboration (confidence/agreed_sources bump) no longer fires for two hits from the same source under fuzzy match -- only a genuinely independent source now counts, per docs/06's own ">=2 independent sources agree" definition -- and a merged record's provenance array now actually includes the corroborating source's fields instead of only a bare source-name string. - services/api/orchestrator/main.py: create_order's int(...) on unvalidated client volume/confidence fields raised on any decimal-formatted input (int("92.5") -> ValueError); now coerced via float() first with a safe default. /vehicles/{plate} and /screen also now catch connector exceptions and degrade gracefully, matching pipeline.py's existing pattern for the same connectors. - lib/orders-store.ts: postgres.js (pinned v3.4.9) returns bigint/numeric columns as strings by default; getOrder/listOrders/setStatus were silently handing callers stringified money fields. Added a narrow per-row coercion instead of touching the shared client's global type map (which would affect unrelated tables' bigint/numeric columns too). - lib/subscriptions-store.ts: the in-memory fallback returned oldest-first while the Postgres branch explicitly orders newest-first; now consistent, mirroring the identical fix already in orders-store.ts. - app/api/auth/login/route.ts: DEV_LOGIN_KEY was compared with a plain !==, a non-constant-time comparison; replaced with crypto.timingSafeEqual. Verified before commit: tsc --noEmit clean; vitest run 88 passed, 1 todo, 0 failures (13 files, unchanged from before this commit -- no existing test's assertions needed to change). services/api/workers/resolve.py's source-novelty and provenance-merge logic additionally verified by direct execution against synthetic same-source/cross-source inputs (no Python test suite exists in this repo to add to). services/api/workers/pipeline.py and services/api/orchestrator/main.py verified with python -m py_compile (no httpx/fastapi installed in this environment to run them directly). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(engine): treat explicit seeds:null as missing to avoid 500 in run_job Four-eyes follow-up to this PR's own seeds change. Switching from `config.get('seeds') or DEMO_SEEDS` to `config['seeds'] if 'seeds' in config` correctly stopped an explicit empty {} from being backfilled with DEMO_SEEDS, but regressed on client-controlled `{"seeds": null}`: seeds became None and the downstream `seeds.get(market, [])` raised AttributeError -> 500. That path is reachable (POST /orders stores raw body.config; POST /orders/{id}/run passes it straight to run_job). Using `config.get('seeds') is not None` keeps the intended behaviour (explicit {} honoured, never silently seeded with demo data) while restoring the None-tolerance the old idiom had. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>Merges github/main (6 commits: 17-bug audit fix, CI quality gate, .env.example + .gitignore fix, missing platform clients + registry services, docs accuracy overhaul) into gitea/main (8 commits: Iceberg write boundary + live ST0RE verification, lakehouse mapping routes, Waves 1-4 foundational hardening). 16 conflicts. Both sides had independently written the four src/clients/*.ts and the two registry services, so those are combined rather than picked: - st0re-client.ts: GitHub's endpoint wins on evidence. POST goes to /v1/st0re/artifacts/register, not /v1/st0re/artifacts -- verified in sax3l/st0re src/api/routes/artifacts.ts, where the bare path is a GET-only list route. Gitea's URL normalisation, error-body text and mock kept. - n0d-client.ts: field is `status`, not `state`, with N0D's real 13-state union -- verified in sax3l/n0d packages/schemas/n0d.job.schema.json, which says explicitly not to add "succeeded". Gitea's mock echo, URL normalisation and error-body text kept. - cl0ud-client.ts: Gitea's fail-closed HTTP normalisation kept (a 200 with no `decision` reads as "deny", not as a silent allow). GitHub's PolicyDecision/PolicyDecisionValue naming kept; PolicyResult retained as an alias. - aud0-client.ts: permissive union of both input types (optional payload/metadata/correlation_id), GitHub's echo return, Gitea's URL normalisation and error-body text, plus a JSON-parse fallback so a successful POST with an empty body does not throw. - dataset-service.ts / source-service.ts: GitHub's record/DTO split and event naming (0pt.source_created / 0pt.create_source, which the merged docs already reference) plus Gitea's name validation, description / created_by fields and actor_id derivation. List filters are the union. - lakehouse mapping routes: GitHub's shared lakehouse-mapping-registry singletons (without which every write 400'd "mapping not found") AND Gitea's Zod parseBody validation. Both, not either. - src/types/source.ts, src/types/dataset.ts: unions of both vocabularies; GitHub's `access` legal-sourcing field kept. - .env.example: GitHub's audited inventory plus Gitea's Zitadel / AUTH_ENFORCE block. - CLAUDE.md, docs/ICEBERG_WRITE_BOUNDARY.md: GitHub's corrections kept, Gitea's live-verification kept and marked as superseding the "would 400 outright" finding (Gitea had already added the required X-Tenant-Id/X-Actor-Id headers). - pnpm-lock.yaml: regenerated with pnpm 9.15.9, not merged. Also corrects CLAUDE.md's claim that the banned services/scrapers stack was "since removed" -- the removal PR is open, not merged, and this branch inherits that code from Gitea main. Nothing here resurrects, extends or re-wires any of it; services/scrapers/ is byte-identical to Gitea main. Verified on the merged tree: typecheck (src/ + tests/) clean; 17 test files, 121 passed, 1 skipped (the realinfra test skipping honestly on ST0RE HTTP 401), 0 failures. Gitea main baseline was the same 121/1; GitHub main baseline could not even `pnpm install` ("packages field missing or empty") -- Gitea's pnpm-workspace.yaml fix resolves that. Whole-repo `typecheck:app` reports 9 errors, an identical set to Gitea main's baseline, so it is advisory in both CI workflows rather than deleted. Lint: 16 problems, identical to Gitea baseline. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>