Synaplan
開源 AI 平台,供企業使用:自行託管的 AI 代理、符合 GDPR 的聊天機器人小工具、RAG 知識庫、WhatsApp 與 Outlook AI、MCP、多模型路由。Docker、Kubernetes 或託管於 Germ
AI 翻譯
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Chicago pin: Qloo taste + licence survival. Who should rent this unit?
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗Click the map. One number answers Who should rent this unit?
At a Chicago pin you get three exhibits on one sheet: who lives here (Census ACS 5-year), who dies (official business licences, n = 5,692), and what the street loves (Qloo taste graph). OSM competitors sit on the map as geography, not as a survival claim. 80% open is not for sale as a rent. It is for sale as a share.
The pin is the only required action. Print memo is a one-page report from that pin. A cell is a block to scout, not a vetted address. Qloo answers who belongs here. Licences answer survival: location did not predict it (κ = 0).
Live demo: https://tastefit-776637567140.us-central1.run.app — pin Wicker Park, read the number, run the Qloo agent. Silent reel: https://tastefit-776637567140.us-central1.run.app/walkthrough.
A silent 60-second reel is at /walkthrough on the running app (source: demo/walkthrough.html). Freeze a beat with ?scene=1 … ?scene=5. A recorded cut is demo/tastefit-60s.mp4 (60 seconds, product only).
GitLab's under-3-minute automation ask is the live pipeline and Duo flows, not this reel. There is no pipeline screen recording in the repo. Paste pipeline 2984949822 and the flows in .gitlab/duo/flows/ (Issue Planner, Developer, Domain Reviewer, Pipeline Medic, Release Scribe).
The judging page is the pin. /spot (Census + pack), /ask (licences), /taste (Qloo exhibit, offline without a key). An older LLM agent still runs a tool loop over the official Qloo hackathon harness (qloo exec) at /run and /tenant:
| Agent tool | Qloo workflow | Purpose |
|---|---|---|
find_tags | find_tags | Turn the concept ("Korean fried chicken counter") into Qloo tag URNs, choosing the right one among same-name tags |
area_taste | recommend (place, location signal) | Places the area's audience has the highest affinity for |
competitors | recommend (place, concept tags, location filter) | Existing places that match the concept: competition and benchmarks |
hotspots | where_popular | Sub-areas where the strongest benchmark over-indexes |
site_math | local (geo.py) | Barycenter site analysis over everything gathered, shown on a map |
geo.py)Hotspot cells come with coordinates; places carry an address, geocoded with OpenStreetMap Nominatim (keyless, max 1 request per second).
c = Σ wᵢ pᵢ / Σ wᵢ.Σ wᵢ·K(x, dᵢ) − 0.7 · Σ wⱼ·K(x, cⱼ), with a Gaussian kernel K whose width follows the demand spread (0.3 to 3 km). This is a gravity model: be close to demand, away from look-alike competitors.The map uses Leaflet with OpenStreetMap tiles. The suggested spot points to a block to scout, not a vetted address.
The memo cites places, tags and numbers returned by Qloo, OpenStreetMap supply counts attributed as such, and, when the city is Chicago, the committed City of Chicago Data Portal community-area footfall shares with their dataset id and date range. It does not attach those Chicago counts to another city, and a name such as South Loop is not given the Loop's totals. It keeps Qloo results separate from interpretation, and ends with a Limits section: Qloo describes aggregate group affinities, not foot traffic, rents or individual behaviour. No personal data is sent to Qloo; inputs are a concept and a place name. The footfall line is a community-area total, not a count at the door and not a vetted address.
/scan, /scan/unit)Two read-only JSON endpoints serve prebuilt city packs (packs/, built offline by cityscan_publish.py; method in METHOD.md). The server never runs the model and makes no network call for them.
GET /scan?city=paris&concept=specialty%20coffee&top=10: the cells of the city where the concept fits best, with reasons, nearest competitors and a 90% Monte Carlo interval on each score.GET /scan/unit?city=paris&lat=48.865&lon=2.377: for one unit, the highest monthly rent each tenant type (restaurant, fast food, bar/café) can pay and still reach the city's official 3-year survival rate, or "not viable at any rent" where even rent-free falls short. This is calibrated on official survival for Paris (SIRENE, EUR) and Chicago (business licences, USD). Other cities say "not calibrated" rather than borrow these numbers.data/us_fafh.json (build_us_fafh.py).TasteFit is a new project, started Oct 10, 2026. The live product is pin-first: who should rent this unit.
All calls go through the official harness (qloo exec); 8 of its 9 workflows are used.
| Workflow | Where | Why |
|---|---|---|
recommend (place, location signal) | pin | What the area's audience over-indexes on: the taste a tenant would sell into |
recommend (place, tag + location filter) | pin | Benchmarks for a concept in the city, and the existing supply inside the area where no OpenStreetMap venue file ships for the city |
entity_tags | tenant pipeline | The tags behind the area's favourite places (cuisine, vibe, price) |
audience_demographics | tenant pipeline | Age and gender skew of the area's top place, shown as relative bars |
find_tags | pin | Turn a concept into a Qloo tag URN; the choice and the alternatives are shown |
rank | tenant pipeline | One call ranks every benchmark for the area's audience, so the demand scores are comparable (scores from separate calls are not); a second ranks the same benchmarks for the whole city, for the over-index |
compare_audiences | tenant pipeline | Shared taste between the area's favourites and the top concepts' benchmarks |
where_popular | pin | Where the top benchmark's audience concentrates, and how far the unit is from it |
trends | tenant pipeline | Reach of the top concept's reference chain (trends cover brands, not places) |
describe is not needed: recommend already returns each place's description. A tenant run makes at most 25 Qloo calls; a cache that lives for one request makes repeated calls free and is dropped when the run ends.
For a unit clicked in Wicker Park, Chicago, step 5 ranks the six concepts' Chicago benchmarks for the local audience:
{"workflow": "rank",
"input": {"options": ["<benchmark id: taqueria>", "<benchmark id: ramen shop>", "..."],
"option_type": "place", "signal_location": "Wicker Park"}}
Qloo returns an affinity per option. TasteFit turns the order into a demand score from 0 to 100 within that call, adds the white-space count from recommend with the concept's tag and the area as filter, and the overlap from compare_audiences, then joins the City Scan rent ceiling for the clicked cell. The page shows every step with the request it sent; no Qloo response is stored.
backtest_chicago.py checks the tenant agent's demand ranking against what actually opened and survived. The ground truth is the City of Chicago Food Inspections register: a restaurant or bakery opened when its licence's first passing License inspection ever falls in 2023-2024 (licences that passed one earlier, such as renewals, are left out), and survived when no later inspection says Out of Business and it was inspected again since April 2025. Names are matched to the ten catalog concepts by keyword (200 of 1,751 openings match). For each area (1.2 km radius), the ten concepts are ordered by one Qloo rank call, and by the citywide share of surviving openings without that area (a "what is opening everywhere" baseline). The output is numbers only (backtest_chicago.json).
| 9 Chicago areas, 31 surviving openings | Qloo demand | Citywide baseline | Random |
|---|---|---|---|
| Surviving openings whose concept is in the top 3 | 29% | 68% | 30% |
| Mean rank correlation with surviving openings | 0.02 | 0.31 | 0 |
| Survival rate of openings in the top-3 concepts (vs. the rest) | 64% of 14 (44% of 50) | 49% of 43 (48% of 21) |
Each opening counts once, for the nearest area centre, because neighbouring areas overlap. 3 of the 12 areas are not scored (Pilsen, Chinatown, River North): they had no surviving catalog opening, or Qloo's rank call returned no usable ranking (2 areas). So this is 9 areas and 31 openings, a small sample.
Result: no gain. Qloo's demand ranking did no better than random at naming the concepts that opened and survived, and the citywide baseline did clearly better: the Qloo-minus-baseline difference has a 90% bootstrap interval of -66 to -4 points. Openings in the concepts Qloo ranked highest did survive more often (64% vs. 44%), but on 14 openings that is not evidence. Earlier runs the same day, before each opening was counted once, gave Qloo between 30% and 54%, so the rank output also varies between runs. The LLM-only column needs a working LLM for every area and is not reported. Qloo and the LLM describe today's taste, after these places opened, so this checks agreement with what survived, not a forecast made in 2023.
Taste Gap Index. The second hypothesis was that Qloo helps once combined with supply: a concept the area loves more than the city does and that is under-served nearby. tgi.py computes, per concept:
The backtest (--food, results in backtest_chicago_tgi.json) uses an OSM snapshot dated 2023-01-01, before the window, so supply has no look-ahead; D still comes from today's Qloo. The four orderings were fixed before the run. Same 9 areas and 31 surviving openings:
| Top-3 hit rate (rank correlation) | |
|---|---|
| Qloo rank alone (as above) | 29% (0.02) |
| Over-index D alone | 35% (−0.04) |
| OSM under-supply alone | 32% (−0.10) |
| Taste Gap Index | 48% (−0.16) |
| Citywide baseline + Taste Gap Index | 55% (0.23) |
| Citywide baseline alone | 68% (0.31) |
Result: no measurable gain. The index beats the plain Qloo rank by 19 points, but the 90% interval of that difference (−13 to +42 points) includes zero, and every TGI variant is below the citywide baseline (index minus baseline: −36 to −6 points; adding the index to the baseline makes it worse, −22 to −6). On 9 areas, what is opening everywhere in Chicago predicts better than local taste or local gaps. The app still shows the index, labelled uncalibrated, because it is the clearest way to explain a recommendation; OSM supply also replaces four Qloo calls per run.
Footfall ("demand already there"). build_chicago_footfall.py writes data/chicago_footfall.json. Counts only, from the City of Chicago Data Portal, under the city's data disclaimer. No trip rows, no rider identifiers, and no Divvy gender or birth year. Every window ends before 2023-01-01.
| Series | Dataset | Window |
|---|---|---|
| TNP evening drop-offs, trip end hour 17 through 21 | m6dm-c72p | 2022-01-01 to 2022-12-31 |
| TNP weekend drop-offs, each Saturday and Sunday | m6dm-c72p | 2022-01-01 to 2022-12-31 |
| Divvy trip ends, placed by the station coordinate | fg6s-gzvg | 2019-01-01 to 2019-12-31 |
| CTA 'L' station entries, stations from 8pix-ypme | 5neh-572f | 2022-01-01 to 2022-12-31 |
The Divvy portal file ends 2020-01-21, so 2019 is its last full year and is still before the backtest. A Saturday evening trip is in both the evening series and the weekend series. A point is counted in the community area whose polygon contains it (boundaries igwz-8jzy). L stations outside the 77 areas, including the suburban ends of the system, are left out: 5,105,024 entries. A zero means the polygon contains no station, not that the file is missing. Hyde Park's polygon contains none in this join.
The concept score is a stated weight vector (footfall.WEIGHTS, each row sums to 1, fixed before this run, not fitted) dotted with the area's share of each citywide total. Dinner concepts load on evening and weekend drop-offs. Daytime concepts load on CTA entries and Divvy ends. The area total is the same for every concept; the weights are what change the order.
backtest_chicago.py --footfall rescores the same 9 areas and 31 survivors with the OpenStreetMap food snapshot dated 2023-01-01. No Qloo call. Over-index D is 0, so the Taste Gap column here is the supply leg plus footfall, not the published Taste Gap Index.
| Ordering | Top-3 hit rate | 90% interval versus this run's citywide baseline |
|---|---|---|
| Footfall alone | 39% | −52 to +3 points |
| Footfall + Taste Gap supply | 39% | −44 to −15 points |
| Footfall + citywide baseline | 68% | 0 to 0 points |
| This run's citywide baseline | 68% |
No footfall variant beats the citywide 68%. Adding footfall to the baseline did not change the top-3 hits. The counts are community-area totals, not a count at the door and not a vetted address. The page at /evidence quotes backtest_chicago_footfall.json. A tenant run reads the committed file and adds no Qloo call.
What this means for the product: Qloo is useful here as evidence a broker can show (what the area loves, who already serves it, where the audience is), not as a validated predictor of tenant survival. Every tenant memo's Limits section reports this backtest result (tenant.BACKTEST_NOTE, added by code if the LLM leaves it out).
Survivorship and intervals (backtest_bias.py, numbers in backtest_bias.json). A Heckman selection correction (Heckman 1979) is not estimated. It needs an exclusion restriction, and this file has none: a licence is missing from the survivor count because it closed, because it was not inspected again, or because its name matched no catalog keyword. Those are not separable from survival, and applications that never opened are not in the register.
Inverse-probability weights (Horvitz and Thompson 1952) are used instead. The propensity in a concept-by-area cell is survivors / openings. A cell with openings and no survivor is dropped, not filled in. The weights cover keyword-matched licences only. On a register pull with no Qloo call (10 areas that have a survivor, including River North, which the 9-area Qloo table left out), the citywide baseline's top-3 hit rate is 0.686 on 35 survivors and 0.766 after weighting (64 weighted openings; 10 openings dropped because the propensity was 0). Weighting does not reverse the published result. The Qloo column was not recomputed: rank orders are not stored, and this pull made no Qloo call.
A split-conformal interval (Vovk, Gammerman and Shafer 2005) around the leave-one-area citywide survivor share, 90%, has quantile 1 and mean width 2 on a rate that lives in [0, 1]. Coverage on the test half is 1 because the interval is the whole range. It does not tighten the claim, and it is not an interval for a Qloo fit score. Those scores are not stored.
decision.py)Three additions turn the evidence into a decision. None of them uses an extra Qloo call.
trends covers one reference brand, not each concept.rent_max) on that concept's own top check, or covers per day when that concept has no value-of-information result. Another concept's check is not reused. A NO-GO top concept does not get a pop-up, and a non-positive line is omitted. The figures use TasteFit's stated cost assumptions, not the reader's. The line is: covers per day, ticket or days at least that line, or staff, other fixed costs or a cost share at most that line. The memo also states the line at the top of the ceiling's 90% range. The plan's SHA-256 covers the area, the concept, the check, the direction, the ceiling, the threshold, the weeks and the UTC time. The outcome log (observed value, weeks run, cleared) has a second hash. Neither is inside the five-field prediction fingerprint, so the Chicago batch hash is unchanged. A pop-up is not a vetted address.rank call; two runs can order close concepts differently.filter_location is fuzzy for small neighborhoods (a Wicker Park filter can return Lincoln Park places).Requires Python 3.10+ and Node.js 22.19+ (for the harness).
git clone https://gitlab.com/gitlab-ai-hackathon/transcend-october-2026/43239248/tastefit.git && cd tastefit
npm ci # installs @qloo/qloo-harness locally (no Python packages needed)
export QLOO_API_KEY=... # Qloo hackathon key, never commit it
export QLOO_BASE_URL=https://hackathon.api.qloo.com
export GEMINI_API_KEY=... # optional: free Google AI Studio key. Tries gemini-3.8-flash (8s), then gemini-3.1-flash-lite
export LLM_BASE_URL=... # optional fallback: any OpenAI-compatible endpoint (default: a free keyless one)
python app.py # http://localhost:8000
python backtest_chicago.py --no-llm # optional: the Qloo backtest above (public Chicago data, about 35 Qloo calls)
python backtest_chicago.py --public-only # register only: IPW and conformal, no Qloo call
python tgi.py build chicago 2023-01-01 /tmp/chicago_2023.json.gz && \
python backtest_chicago.py --no-llm --food /tmp/chicago_2023.json.gz # optional: with the Taste Gap Index
The API keys stay on the server; the browser only receives the streamed steps and the memo. LLM calls go to Gemini first when GEMINI_API_KEY is set, then to the fallback endpoint. The tenant match never depends on an LLM to finish: without one it uses a fixed candidate list and a template memo.
CLI: python agent.py "Korean fried chicken counter" "Austin, TX"
MCP server (mcp_server.py, standard library, stdio JSON-RPC): exposes tenant_match, city_scan and city_scan_unit to any MCP client. The key comes from the client's environment and is redacted from every tool result.
{"mcpServers": {"tastefit": {"command": "python3", "args": ["/path/to/tastefit/mcp_server.py"],
"env": {"QLOO_API_KEY": "...", "QLOO_BASE_URL": "https://hackathon.api.qloo.com"}}}}
Tenant match (GET /tenant?lat=41.9088&lon=-87.6796&rent=4000, or ?city=Paris&location=Le%20Marais): for a vacant unit, which food concepts the local audience wants that nobody nearby serves, and whether each can pay the rent. A fixed pipeline of at most 25 Qloo calls (8 workflows; a cache that lives for one request only, so repeated calls within a run are free and nothing Qloo returns outlives the request) streams each step, then a scored shortlist, a "without Qloo" comparison and a memo (tenant.py).
Claim ledger (the "without Qloo" panel): the same LLM, with no tools, suggests concepts and names up to 5 competitors within 500 m. Each concept is marked confirmed (Taste Gap "white space", or "neutral" with a positive over-index), contradicted ("proven demand, crowded", "empty for a reason", "avoid"), partly supported, or not tested by this run. Each named venue is looked up in OpenStreetMap within 500 m with City Scan's name matcher (similarity >= 0.6 found, 0.45-0.6 ambiguous); Qloo's own area favourites that read as food venues go through the same check (1.5 km, since they are area-level; the OSM file holds food venues only). The page shows counts, and calls them agreement with the run's evidence and existence in open data, not accuracy: OSM misses real venues too. Supply estimates whose city-wide prior carries >= 80% of the weight are badged "Data coverage: low": the local OSM count barely moves them. This happens when the expected count near the unit is small or the city's block counts vary little; it is not a measure of how complete OSM mapping is.
Front end: index.html plus static/ (app.js, styles.css, icons), no build step. The map starts on Wicker Park; that pin is geography, not a vacancy listing. "Try the Wicker Park example" drops the pin and opens Print memo (live /spot and /taste). demo/wicker-park.json remains in the repo as an aggregates-only snapshot (GET /wicker-example.json); it stores no Qloo response.
Tests: python -m unittest discover -s tests -t . -v (389 tests).
Live demo (Cloud Run, free tier): https://tastefit-776637567140.us-central1.run.app (us-central1, 0 to 1 instances). The deploy-gcp job copies the signed, scanned CI image by digest to Artifact Registry and deploys that digest, authenticated by Workload Identity Federation (no stored key); smoke-gcp checks that /health returns the commit SHA.
Paste the Cloud Run URL. A Render service may still exist from earlier deploys; it is not the live demo.
TasteFit started on GitHub (https://github.com/youssefbenzidiya-cmd/tastefit) with no CI, no tests and no post-deploy check. On GitLab, every commit goes through a 7-stage pipeline, and four custom GitLab Duo Agent Platform flows plan, fix, review and release the project when events happen, without being asked. A person merges every change.
Pipeline (.gitlab-ci.yml): test → build → security → package → deploy → verify → release. Latest main pipeline: 2984949822 (merge of feat/pin-first). An earlier main pipeline (2935652252) passed all 15 jobs then listed: unit-tests, lint, flows-lint, image-build, semgrep-sast, secret_detection, dependency-scanning, container_scanning, cve-gate, sbom, sign-image, verify-signature, deploy-render, smoke-test, release. A daily schedule (06:00 UTC) rebuilds and rescans the image and checks that production is up.
| Stage | What automates it | Evidence |
|---|---|---|
| Plan | Issue Planner turns a new issue into a scoped plan with labels and open questions. Issues assigned to GitLab's Duo Developer flow become MRs. | #15 (planner), #5 (Duo Developer) |
| Create | Duo Developer opens feature MRs; Pipeline Medic opens fix MRs | !4, !1, !9, !11 |
| Verify | Unit tests with a JUnit report and coverage (74%), ruff lint, flows-lint of the Duo flow files; Medic checks its fixes with the CI linter | pipeline 2935652252 (159 of 159 passed) |
| Package | Docker image built, started and health-checked, then pushed to the GitLab container registry; CycloneDX SBOM; cosign keyless signature, verified in CI | container registry, jobs sbom, , |
Definitions are in .gitlab/duo/flows/; rules are in AGENTS.md and skills/tastefit-guardrails/. Each flow runs as its own service account with an explicit toolset. No flow has a merge tool.
| Flow | Runs when | What it does |
|---|---|---|
| Issue Planner | an issue is created | Rewrites the issue as a scoped plan with acceptance criteria, sets labels, and lists the questions a person must answer before work starts. |
| Duo Developer (GitLab foundational flow) | an issue is assigned to it | Implements the issue and opens a merge request. |
| Pipeline Medic | any pipeline fails | Reads the failing job logs, classifies the failure, and opens a minimal fix MR on medic/<pipeline id>, checked with the CI linter. It looks for an existing MR first and adds a note instead of a duplicate. On medic/ and duo/ refs it never opens a fix, so it cannot loop. The definition splits the work between a log_analyst and a fixer subagent. |
| Domain Reviewer | a merge request is created, or the bot is assigned as reviewer | Checks the 9 product rules a generic reviewer cannot know (secrets, Qloo data terms, privacy, Nominatim policy, site-maths tests, memo honesty, stdlib-only server, protected security jobs, agent governance), posts inline suggestions, and answers GO / ADJUST / NO-GO, like TasteFit itself. |
| Release Scribe | a pipeline on main passes | Writes release notes for restaurant owners with deployment proof (deployed SHA, smoke-test job) and a security summary across all scanners. Opens a grouped issue for High or Critical findings, and posts a note on issues the release shipped. |
Every row links to a public page in this project.
| Claim | Evidence |
|---|---|
The smoke test went red after a good deploy at 22:16:08 UTC. It was a false negative: curl exit 23, caused by grep -q closing the pipe under pipefail. | pipeline 2934704707, failed job 17087586018 |
| Pipeline Medic opened a one-line fix MR at 22:17:47, without being asked, and validated it with the CI linter | !1, commit 4d905e3e |
| A second Medic run found that MR and added a note instead of a duplicate | !1 (note at 22:18:15) |
| Domain Reviewer posted GO with its rule table at 22:19:51; a person merged at 22:20:38; the next pipeline deployed and passed | !1, pipeline 2934713922 |
Medic fixed a failed release job (shell-evaluated backticks in the release description) | pipeline 2934914083, !9 |
Medic fixed a lint failure on ; the next pipeline passed all 15 jobs and published release v1.86 |
The loop, measured. Time from the failed job to the Medic fix MR, on every real failure so far:
| Failure | Job failed (UTC) | Fix MR opened | Delay | Review | Merged by a person |
|---|---|---|---|---|---|
smoke-test on main | 22:16:08 | !1 22:17:47 | 1 min 39 s | GO 22:19:51 | 22:20:38 |
release on main | 01:08:07 | !9 01:10:03 | 1 min 56 s | GO 01:12:46 | 01:12:51 |
| lint on a research branch | 08:20:41 | !10 08:22:29 | 1 min 48 s | GO 08:35:15 | 08:37:10 |
lint on main | 08:29:29 | !11 08:30:39 | 1 min 10 s | GO 08:35:13 | 08:37:06 |
Review times depend on when a person assigned the reviewer (see Limits).
On 11 Oct 2026 we opened eight merge requests through the API. Each had one planted problem, and none had a hint in its title or description. We recorded what the agents did without stepping in. All eight are now closed, unmerged.
| Planted problem | MR | Expected | Domain Reviewer (time to verdict) | Pipeline / Medic |
|---|---|---|---|---|
| Harmless copy edit | !16 | GO | GO (47 s) | green |
| Slug limit that breaks unit tests | !17 | red pipeline, fix MR | ADJUST (54 s) | unit-tests red; Medic fix !25 restores the limit |
| Hardcoded (fake) API key | !18 | NO-GO (rule 1) | NO-GO (51 s) | unit-tests red; Medic fix !24 removes the key and restores the guard |
| Qloo responses written to disk | !19 | NO-GO (rule 2) | NO-GO (54 s) | green |
| Secret detection disabled | !20 | NO-GO (rule 8) | NO-GO (55 s) | green |
geo.py default changed, no test | !21 | ADJUST (rule 5) | NO-GO (53 s) | unit-tests red; Medic fix !26 restores the default |
What it shows:
The baseline is GitHub commit 8c465c1 (2026-10-10, 19:08 UTC), the last commit before any GitLab work. The next commit, fda392a (19:22 UTC), added the first tests, /health and a first .gitlab-ci.yml.
Before (GitHub 8c465c1) | After (GitLab main) | |
|---|---|---|
| Tests | none | 164 unit tests with JUnit and coverage, plus lint, on every push |
| CI/CD | none | 7-stage pipeline: test, build, security, package, deploy, verify, release |
| Image | node:22-slim (Debian 12), built by the host, never scanned | node:22-alpine, built in CI, started and health-checked, signed, with an SBOM |
| Container findings | not measured (the same Dockerfile later scanned at 338: 5 Critical, 67 High) | 0 on the image GitLab builds from the deployed commit |
| Image size | 141 MB compressed (same Dockerfile, measured in the GitLab registry) | 100 MB (−29%) |
| Deploy | Render web service, no post-deploy check | exact commit, verified live by /health returning that SHA |
| Releases | none | GitLab Releases with image digest and SBOM, plus owner-facing notes from Release Scribe |
| Broken pipeline | noticed by chance | fix MR opened by Pipeline Medic in under 2 minutes, 4 times so far |
| Review | none | Domain Reviewer on every MR, 9 product rules |
| Agents | none | Issue Planner, Pipeline Medic, Domain Reviewer, Release Scribe, plus GitLab's Duo Developer |
cve-gate covers container findings only. Dependency scanning reports 4 High findings (undici, brace-expansion), tracked in #12, and they do not fail the pipeline.c1605d83 (#9). Current scans of the Alpine image show 0.cityscan.py:1418, geo.py:87 and agent.py:110) appear in every release note and are not yet triaged.create_vulnerability_issue rejected our calls, so Release Scribe groups findings in one issue.MIT
METHOD.mdpython3 docs/verify_certificate.py docs/published-street.json: exit 0 when no replacement or swap raises the surplus. That file is stated scores and rents, not a Qloo response, and the units are an example line, not vetted addresses. On 2026-10-15 the server ran four representative monthly rents (2600, 2400, 2200, 2000 USD) at the example point near Milwaukee Avenue, Wicker Park. They are not confirmed vacancies and not a vetted address. /evidence keeps the street-certificate line and the event kinds with timings. No Qloo response is stored. One step hit a rate limit. Production /tenant returned offline, so the run used this server locally.GET /street. It makes no Qloo call. The app's access log records the path and not the query. A platform request log can still store the full URL.data/ledger.json does not store fit scores. Committed batches are on /ledger. The pre-registered score is the Brier score (Brier 1950): the mean of (p - o) squared. p is a fixed map of the verdict label already inside the fingerprint (GO 0.66, ADJUST 0.50, NO-GO 0.33), not a Qloo fit score and not a calibrated probability of opening. A batch with any other verdict has no p. o is 1 only when a new Chicago food licence matching the frozen keyword appears before the check date. Skill is the Brier score of one constant citywide rate, computed on the check date from the same public file, minus TasteFit's Brier score. The 2023-2024 backtest found no measurable gain, so a negative skill score stays a possible result. The Chicago batch registered on 2026-10-14 has verdict unscored: the rank call returned a rate limit, so no GO / ADJUST / NO-GO label was produced, and the Brier score is not defined for that batch. The first listed concept is a catalog entry, not a ranking. The registered point is a map point, not a vetted address.sign-image| Secure | SAST, secret detection, container scanning, dependency scanning, and cve-gate (fails on Critical or High container findings that are not in security/cve-allowlist.txt); Release Scribe triages findings | #1, #2, !3, #4, cve-gate job |
| Release | Render deploy pinned to the commit SHA as the GitLab production environment; GitLab Releases with the image digest and SBOM; Release Scribe notes for owners | environment, releases, #19 |
| Configure | Environment-based config (no secrets in code), CI/CD variables for deploy credentials, Dockerfile HEALTHCHECK checked by image-build | .gitlab-ci.yml, Dockerfile |
| Monitor | Smoke test waits until /health returns the deployed SHA; daily uptime check with response-time metrics | pipeline 2934713922, scheduled pipeline 2935294308 |
| Govern | 9 rules in AGENTS.md enforced by Domain Reviewer; CODEOWNERS; policy-guard job fails an agent branch (medic/, duo/) that edits agent policy, CI scripts or the CVE allowlist | !5 (NO-GO under rule 8); policy-guard passed on the MR pipelines of !8, !12 and !14 |
main| pipeline 2935452861, !11, pipeline 2935462422, v1.86 |
Medic also fixed a lint failure on a research branch | !10 |
| Domain Reviewer answered NO-GO to our own change that skipped security jobs on docs-only commits (rule 8), then GO after rework | !5 |
| Domain Reviewer answered NO-GO on a real bug: the Chicago rent ceiling was fitted under the Paris rent share (5-10-15%) instead of Chicago's (4-6-10%). After the fix the restaurant ceiling went from $5,970 to $3,230. Two ADJUST rounds followed (a test that only checked metadata, then docs), then GO. | !14 |
| Plan to production: Issue Planner planned #15 within 90 seconds of its creation (acceptance criteria, test plan, AGENTS.md check) and asked for decisions; a person answered and implemented it in !12; Domain Reviewer answered ADJUST (5 findings), then GO after the fix; merged, deployed, released as v1.96, and Release Scribe noted the shipment on #15 | #15, !12, pipeline 2935534874, v1.96 |
| Issue assigned to Duo Developer became an MR with three new tests, got GO, and the live footer showed the new build | #5, !4, release notes #8 |
| Release Scribe's first notes reported 338 container findings (5 Critical, 67 High) and it filed a grouped security issue | #1, #2 |
| Alpine rebuild: 0 container findings on the image GitLab builds from the deployed commit, still 0 today | !3, #4, #19 |
| Image 141 MB → 100 MB compressed (−29%) | container registry (tags c1605d83 vs ac836f36) |
| Six GitLab Releases so far (v1.58 to v1.120), each with the deployed commit, the signed image digest and an SBOM link | releases, e.g. v1.120 |
| Daily scheduled rescan and uptime check | schedules, scheduled pipeline 2935294308 |
| Production environment with per-commit deployments | environment production |
| Memo told to give an exact street address | !22 | NO-GO (rule 6) | ADJUST (51 s), then NO-GO under Reviewer 1.2.0 | green |
duo/ branch edits AGENTS.md | !23 | policy-guard fails, NO-GO (rule 9) | NO-GO (57 s) | policy-guard red; no Medic fix (agents may not touch policy) |