Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
72 commits
Select commit Hold shift + click to select a range
a40df20
docs: ADR-0033 — registry evidence replaces the name-distinctiveness …
ydimitrof Aug 5, 2026
4fde377
docs: ADR-0023 carries its superseded status
ydimitrof Aug 5, 2026
ea00781
test(seed): conflict-of-interest smoke fixture for local development
ydimitrof Aug 5, 2026
b364031
feat(tr): registry client, deed cache and the ЕИК checksum as a Node …
ydimitrof Aug 5, 2026
f8b198f
feat(tr): deed parser and the six-rung evidence ladder
ydimitrof Aug 5, 2026
98ac4f5
feat(tr): the deed crawler — paced, resumable, and stopping on a 429
ydimitrof Aug 5, 2026
800b2c1
feat(db): migration 0006 — the Trade Register evidence seal
ydimitrof Aug 5, 2026
5f64f5c
fix(cacbg): closelyHeldForm did not exclude КДА
ydimitrof Aug 6, 2026
4226ae3
feat(cacbg): the evidence ladder replaces the publish tiers
ydimitrof Aug 6, 2026
2a08744
feat(web): the surface explains the registry fact each link rests on
ydimitrof Aug 6, 2026
796903a
docs/ci: publish the rule, and put the decisions on a cadence
ydimitrof Aug 6, 2026
d50a723
fix(test): two fixtures that only passed because of local state
ydimitrof Aug 6, 2026
7a424bd
fix(tr): accept double-quoted attributes in the deed parser
ydimitrof Aug 6, 2026
299400e
fix(tr): anchor the ЕГН guard so a 13-digit ЕИК cannot abort the crawl
ydimitrof Aug 6, 2026
7f2e174
feat(tr): enforce the 35-day deed retention with a purge step
ydimitrof Aug 6, 2026
2327e62
feat(related-persons): implement the monotonicity gate ADR-0033 speci…
ydimitrof Aug 6, 2026
76ed3b2
docs(adr): record ADR-0033's amendments on the ADRs it amends
ydimitrof Aug 6, 2026
935ce1c
test(web): scope the missing-source assertion to the card that has none
ydimitrof Aug 6, 2026
f4d73dc
docs(adr): resolve the §5/§10 control-number gap in ADR-0033
ydimitrof Aug 6, 2026
2b06b76
fix(tr): survive a malformed numeric entity instead of killing the crawl
ydimitrof Aug 7, 2026
95d8c51
test(db): put the evidence seal gate under actual test pressure
ydimitrof Aug 7, 2026
0a61317
fix(db): withhold an unrecognised evidence seal instead of upgrading it
ydimitrof Aug 7, 2026
df76ae7
fix(cacbg): bound the seat matched_fact so a name cannot ride the seal
ydimitrof Aug 7, 2026
2d3c064
fix(seed): seal the dev fixture so /conflicts is not empty on a fresh DB
ydimitrof Aug 7, 2026
26333d8
fix(tr): bound both the response size and the erasure regex's backtra…
ydimitrof Aug 7, 2026
436dbc2
fix(cacbg): re-derive the deed path, and pin the duplicated joint-sto…
ydimitrof Aug 7, 2026
024c26f
feat(cacbg): add --emit-candidates so one job can bootstrap its own c…
ydimitrof Aug 7, 2026
ae661ac
ci(related-persons): run the register crawl inside the decision job
ydimitrof Aug 7, 2026
9555fbf
test(db): type the seal-gate fixture arrays explicitly
ydimitrof Aug 7, 2026
6265854
fix(ingest): запази отрицателната разлика при анекс, който намалява д…
todorkolev Aug 7, 2026
37e1258
build(deps): вдигни nanoid до 3.3.17+ (GHSA-2v37-7h3g-55p8) (#291)
todorkolev Aug 7, 2026
17f9472
fix(scripts): пестеливо качване на свързани лица и сверка след него (…
todorkolev Aug 7, 2026
ddaaacb
fix(etl): изтичащи потоци при четенията от ЕОП и сигнал за успешен кр…
todorkolev Aug 7, 2026
8139adb
fix(etl): never pair a full derive with a partial catch-up window (#270)
todorkolev Aug 8, 2026
8db751d
fix(etl): detect wrangler's SQLITE errors from stdout in safeD1 (#277)
todorkolev Aug 8, 2026
3e047c6
docs(scripts): подсигуряване, а не поправка на наблюдаван провал (#293)
todorkolev Aug 9, 2026
4f4ff60
test(scripts): покрий разпознаването на липсваща таблица в safeD1 (#295)
todorkolev Aug 9, 2026
73e055d
ci: забрани Co-Authored-By към агент, запази трейлърите с хора (#296)
todorkolev Aug 9, 2026
8689212
chore: махни проверката за трейлъри, правилото остава в AGENTS.md (#297)
todorkolev Aug 9, 2026
bf4fa97
fix(tr): refuse a seat confirmation when the declared period is unknown
ydimitrof Aug 11, 2026
466d002
fix(cacbg): give the monotonicity gate a path for the removals it san…
ydimitrof Aug 11, 2026
e5e8709
fix(ci,tr,db): close the remaining review findings on injection, purg…
ydimitrof Aug 11, 2026
aee1427
docs(adr): record the monthly cadence as a decision, not a YAML comment
ydimitrof Aug 11, 2026
36cdd9d
chore(deps): raise nanoid past GHSA-2v37-7h3g-55p8
ydimitrof Aug 11, 2026
4d21ba0
fix(cacbg): bar a listed АД whose seat carries neither comma nor dot
ydimitrof Aug 12, 2026
37a3db8
fix(cacbg): date a filing by its folder when the declared year is unr…
ydimitrof Aug 12, 2026
8e6f1dc
fix(cacbg): do not read an unresolvable holder column as an own stake
ydimitrof Aug 12, 2026
ce5b2ad
fix(tr): require the registry evidence to establish the COMPANY, not …
ydimitrof Aug 12, 2026
e9c17b9
feat(db): constrain the publishing-gate enums and fix the declaration…
ydimitrof Aug 12, 2026
021da5a
fix(db): gate the company-search badge on the evidence seal, and bind…
ydimitrof Aug 12, 2026
b8b4332
fix(web,ci): make the page prose family-aware and default the data jo…
ydimitrof Aug 12, 2026
2039b9f
test: cover the four unexercised audit axes, every ownership field, a…
ydimitrof Aug 12, 2026
da80eaf
fix(db): enforce the retrofit constraints with triggers, not a table …
ydimitrof Aug 12, 2026
013b177
fix(etl): свържи OCDS анексите с договорите през моста tender.id → УН…
cefothe Aug 12, 2026
1ab3dcd
test(db): закови gate-а за близнаци при анексите (#303)
todorkolev Aug 12, 2026
753dd4d
ci(deps): вдигни групата github-actions (5 обновявания) (#292)
dependabot[bot] Aug 12, 2026
daeb6e9
fix(etl): хващай стойност, въведена в стотинки (#298)
todorkolev Aug 12, 2026
426e2ef
fix(etl): хващай сбъркано число в анекс (стъпка >=10x при сбор >=5x) …
todorkolev Aug 12, 2026
e481baa
Merge upstream/main into feat/registry-evidence-links
ydimitrof Aug 13, 2026
ac15498
test: apply migration 0006 wherever the merged suites build a schema
ydimitrof Aug 13, 2026
24c8001
fix(db): leave migration 0003 untouched and let 0007 enforce for ever…
ydimitrof Aug 14, 2026
23a1939
test: assert the seal vocabulary with the production predicate, not a…
ydimitrof Aug 14, 2026
2813726
fix(web,db,docs): render the entry number, bind the join invariant, c…
ydimitrof Aug 14, 2026
9999ad5
fix(etl): поправяй и флагвай двойно броене в стойността на анекс (#307)
cefothe Aug 14, 2026
f8973a2
fix(ci): сверявай псевдонимите в сондата за колоните на анексите (#310)
todorkolev Aug 14, 2026
b17e70c
fix(etl): свържи разминатите по номер анекси през стойностна котва (#…
cefothe Aug 14, 2026
0a97059
fix(db,ci,docs): преномерирай миграциите на 0009/0010 и оправи комент…
todorkolev Aug 14, 2026
0d8be1e
refactor(db): преименувай променливите на миграциите 9/10 в тестовете
todorkolev Aug 14, 2026
90c2f99
Merge origin/main into feat/registry-evidence-links
todorkolev Aug 14, 2026
25d189e
fix(etl): хващай стотинки грешката и по прогнозата на позицията (#304)
todorkolev Aug 14, 2026
227bb4c
Merge remote-tracking branch 'origin/main' into pr309-work
todorkolev Aug 14, 2026
215123d
Merge origin/main into feat/registry-evidence-links (#304)
todorkolev Aug 14, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

## Чеклист

- [ ] Комитите следват [conventional commits](https://www.conventionalcommits.org) и **нямат** `Co-Authored-By:` trailer
- [ ] Комитите следват [conventional commits](https://www.conventionalcommits.org) и **нямат** `Co-Authored-By:` trailer, който сочи към агент (Claude Code, Codex, Cursor, Copilot). Трейлъри с **хора** са наред и не се махат — те са начинът заслугата на сътрудника да оцелее при squash
- [ ] PR-ът е с **един логически обхват** и е от форк към `midt-bg/sigma:main`
- [ ] `pnpm typecheck` минава
- [ ] `pnpm test` (поне за засегнатите пакети) минава
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
container:
image: semgrep/semgrep:1.170.0@sha256:c98f8829eea377274ee4b10656458b078b88232469b2ff913f091c2317347c9d
steps:
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Static analysis (semgrep)
run: semgrep scan --config p/security-audit --config p/secrets --config p/typescript --metrics=off --error

Expand All @@ -55,7 +55,7 @@ jobs:
tar -xzf "$RUNNER_TEMP/gitleaks.tar.gz" -C "$RUNNER_TEMP" gitleaks
"$RUNNER_TEMP/gitleaks" dir . --redact --no-banner

- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
Expand Down
94 changes: 93 additions & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ jobs:
SIGMA_VECTORIZE_NAME: ${{ vars.SIGMA_VECTORIZE_NAME }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
Expand Down Expand Up @@ -145,6 +145,26 @@ jobs:
pnpm --filter @sigma/web exec wrangler d1 execute "${SIGMA_D1_NAME:-sigma}" \
--config wrangler.deploy.jsonc --remote --yes \
--file ../../packages/db/migrations/0003_related_persons_foundation.sql
# 0009 attaches the Trade Register evidence seal (#279, ADR-0033). Both migrations are
# applied by name here because wrangler's migration ledger is empty on this D1 (the base
# schema was created out-of-band), so `d1 migrations apply` would collide on 0000.
pnpm --filter @sigma/web exec wrangler d1 execute "${SIGMA_D1_NAME:-sigma}" \
--config wrangler.deploy.jsonc --remote --yes \
--file ../../packages/db/migrations/0009_interest_link_evidence.sql
# 0010 owns the publishing-gate enforcement for EVERY database (#279 §2) — fresh and already
# deployed alike. It is deliberately NOT declared in 0003: that migration is already applied
# everywhere, `CREATE TABLE IF NOT EXISTS` never revisits an existing table, and the ship step
# wipes ROWS, not definitions — so an in-place CHECK would exist only on databases built after
# the edit, and be absent on exactly the database that serves the site. Enforced with BEFORE
# INSERT/UPDATE triggers, NOT a table rebuild: the create-copy-drop-rename route would expose
# the foreign keys and strip every evidence seal, emptying the public surface until the next
# monthly run (see the migration header). `control_hash` stays NULLABLE by design — the register
# omits it on some declarations; the natural-key index folds those with COALESCE instead.
# Idempotent: every statement is `IF NOT EXISTS` over a converging definition, so re-applying it
# on each deploy is a no-op. Verified by three consecutive applications, gate still enforcing.
pnpm --filter @sigma/web exec wrangler d1 execute "${SIGMA_D1_NAME:-sigma}" \
--config wrangler.deploy.jsonc --remote --yes \
--file ../../packages/db/migrations/0010_publishing_gate_constraints.sql
# The base schema was created out-of-band with `d1 execute --file`, so wrangler's migration
# ledger is empty and `d1 migrations apply` would collide on 0000. Probe the actual table
# instead. SQLite has no `ADD COLUMN IF NOT EXISTS`; a completion-marker table is created only
Expand Down Expand Up @@ -214,6 +234,78 @@ jobs:
;;
esac

# #305 additive columns (migrations 0006/0007) must exist BEFORE the Worker serves: the contract-page
# query reads amendments.value_restated/value_suspect, and promote-amendments/refresh-slice also INSERT
# value_treatment — so all three must be present or the read AND the ETL write fail. Same rationale as
# the currency step above: the migration ledger is empty, so `d1 migrations apply` would collide on
# 0000. Probe the actual table and ALTER only the missing columns (SQLite has no ADD COLUMN IF NOT
# EXISTS). These are pure additions with safe defaults (INTEGER NOT NULL DEFAULT 0 / nullable TEXT), so
# no backfill or completion marker is needed — an ALTER populates every existing row. Malformed
# responses are fatal. This also makes 0006/0007 replay-safe when they were applied out-of-ledger.
# #306 folds in here rather than adding a second step of its own (as PR #308's own note asked):
# refresh-slice.sql and promote-amendments.sql write contract_number_raw + link_method into served
# `amendments`, so the first cron after release would crash on the missing columns. One probe, one
# mechanism — a second hand-written step is another chance for the alias bug #310 had to fix.
- name: Apply amendment restated/suspect + provenance columns
if: steps.guard.outputs.ok == 'true'
run: |
node scripts/wrangler-render.mjs apps/web/wrangler.jsonc
# Each alias below MUST be the column name itself: read_flag looks the row up by the very string
# ensure_column is called with. An alias that merely describes the column (has_restated) makes
# `Object.hasOwn(row, key)` false for every column, which the probe treats as an unreadable answer
# and turns into a hard failure — so the step could never succeed, on any database.
schema_json="$(pnpm --filter @sigma/web exec wrangler d1 execute "${SIGMA_D1_NAME:-sigma}" \
--config wrangler.deploy.jsonc --remote --yes --json \
--command "SELECT
(SELECT COUNT(*) FROM pragma_table_info('amendments') WHERE name = 'value_restated') AS value_restated,
(SELECT COUNT(*) FROM pragma_table_info('amendments') WHERE name = 'value_treatment') AS value_treatment,
(SELECT COUNT(*) FROM pragma_table_info('amendments') WHERE name = 'value_suspect') AS value_suspect,
(SELECT COUNT(*) FROM pragma_table_info('amendments') WHERE name = 'contract_number_raw') AS contract_number_raw,
(SELECT COUNT(*) FROM pragma_table_info('amendments') WHERE name = 'link_method') AS link_method")"

read_flag() {
printf '%s' "$schema_json" | node -e '
const fs = require("fs");
let payload;
try {
payload = JSON.parse(fs.readFileSync(0, "utf8"));
} catch {
process.exit(2);
}
const result = Array.isArray(payload) ? payload[0] : payload;
const row = result && Array.isArray(result.results) ? result.results[0] : null;
const key = process.argv[1];
if (!row || !Object.hasOwn(row, key)) process.exit(2);
process.exit(Number(row[key]) === 1 ? 0 : 1);
' "$1"
}

add_column() {
echo "amendments.$1 missing; adding it."
pnpm --filter @sigma/web exec wrangler d1 execute "${SIGMA_D1_NAME:-sigma}" \
--config wrangler.deploy.jsonc --remote --yes \
--command "ALTER TABLE amendments ADD COLUMN $2"
}

ensure_column() {
set +e
read_flag "$1"
status="$?"
set -e
case "$status" in
0) echo "amendments.$1 already exists." ;;
1) add_column "$1" "$2" ;;
*) echo "::error::Could not determine whether amendments.$1 exists."; exit 1 ;;
esac
}

ensure_column value_restated "value_restated INTEGER NOT NULL DEFAULT 0"
ensure_column value_treatment "value_treatment TEXT"
ensure_column value_suspect "value_suspect INTEGER NOT NULL DEFAULT 0"
# #306 provenance: NULL on both = the row linked by contract_number directly (or is unlinked).
ensure_column contract_number_raw "contract_number_raw TEXT"
ensure_column link_method "link_method TEXT"

# `run deploy`, not `deploy` — bare `pnpm deploy` is a pnpm built-in, not our package script.
- name: Deploy explorer (sigma)
if: steps.guard.outputs.ok == 'true'
Expand Down
Loading