Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 76024e55fe
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Pull request overview
This PR introduces a ServiceTitan-themed sample surface (CLI CRM demo + minimal JSON API) while also adding a new Zeta.Core.SignalQuality module, associated tests, and several factory-process docs/scripts (drop-zone protocol, live-lock audit, operator-input log, etc.).
Changes:
- Add
samples/ServiceTitanFactoryApi(in-memory ASP.NET Core minimal API) andsamples/ServiceTitanCrm(console demo) for CRM-shaped scenarios. - Add
src/Core/SignalQuality.fsplus new algebra/operator tests validating CRM scenarios and signal-quality scoring. - Add factory hygiene tooling/docs: live-lock audit script + history log, drop-zone protocol, and various research/process documents.
Reviewed changes
Copilot reviewed 35 out of 37 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| tools/audit/live-lock-audit.sh | New script to classify recent commits and flag “live-lock” when external-motion ratio is low. |
| tests/Tests.FSharp/Tests.FSharp.fsproj | Registers new test modules (SignalQuality.Tests, CrmScenarios.Tests). |
| tests/Tests.FSharp/Operators/CrmScenarios.Tests.fs | New operator-level scenario tests mirroring CRM demo behavior. |
| tests/Tests.FSharp/Algebra/SignalQuality.Tests.fs | New tests for SignalQuality scoring functions and composition semantics. |
| src/Core/SignalQuality.fs | Introduces SignalQuality module/types for multi-dimension quality measurement and scoring. |
| src/Core/Core.fsproj | Adds SignalQuality.fs to Core compile list. |
| samples/ServiceTitanFactoryApi/ServiceTitanFactoryApi.fsproj | New sample Web API project file. |
| samples/ServiceTitanFactoryApi/Seed.fs | Deterministic in-memory seed data for the JSON API sample. |
| samples/ServiceTitanFactoryApi/README.md | Usage + endpoint documentation for the JSON API sample. |
| samples/ServiceTitanFactoryApi/Program.fs | Minimal API endpoints for customers/opportunities/activities + derived pipeline views. |
| samples/ServiceTitanCrm/ServiceTitanCrm.fsproj | New console CRM demo project file. |
| samples/ServiceTitanCrm/Program.fs | Console demo showing retraction-native CRM operations and views. |
| memory/project_reproducible_stability_as_obvious_purpose_2026_04_22.md | New project memory entry about “reproducible stability” directive. |
| memory/project_operator_input_quality_log_directive_2026_04_22.md | New memory entry describing operator-input quality log directive. |
| memory/project_arc3_adversarial_self_play_emulator_absorption_scoring_2026_04_22.md | New memory entry on ARC-3-style scoring loop concept. |
| memory/project_aaron_drop_zone_protocol_2026_04_22.md | New memory entry documenting drop/ inbox protocol. |
| memory/observed-phenomena/2026-04-19-transcript-duplication-splitbrain-hypothesis.md | New observed-phenomena note tied to an existing artifact. |
| drop/README.md | Adds the tracked drop-zone protocol document. |
| drop/.gitignore | Configures drop-zone to ignore everything except sentinel files. |
| docs/research/zeta-self-use-tiny-bin-file-germination-2026-04-22.md | Research sketch describing a proposed tiny-bin-file “germination” step. |
| docs/research/oss-deep-research-zeta-aurora-2026-04-22.md | Absorption note for an OpenAI Deep Research report. |
| docs/research/openai-deep-ingest-cross-substrate-readability-2026-04-22.md | Research note on cross-substrate ingest/readability tradeoffs. |
| docs/research/meta-pixel-perfect-text-to-image-youtube-wink-2026-04-22.md | Research note capturing an external “pixel perfect T2I” signal. |
| docs/research/cutting-edge-database-gap-review-2026-04-23.md | Research note identifying cutting-edge DB gaps and candidate backlog items. |
| docs/research/arc3-adversarial-self-play-emulator-absorption-scoring-2026-04-22.md | Research doc elaborating the ARC-3 adversarial self-play framing. |
| docs/research/amara-network-health-oracle-rules-stacking-2026-04-22.md | Research doc capturing oracle/stacking critique structure and annotations. |
| docs/operator-input-quality-log.md | Adds operator-input scoring rubric + inaugural log entries. |
| docs/hygiene-history/live-lock-audit-history.md | Adds history log format + inaugural run entry for live-lock audit. |
| docs/AUTONOMOUS-LOOP.md | Updates priority ladder to include drop-zone audit step. |
| Zeta.sln | Adds sample projects and new solution configurations. |
| README.md | Adds “thesis: reproducible stability” section pointing to AGENTS.md. |
| AGENTS.md | Adds “purpose: reproducible stability” and adjusts “ship/break” phrasing. |
| .gitignore | Ignores .btw-queue.md and .playwright-mcp/, fixes node_modules entry formatting. |
| .claude/commands/btw.md | Adds /btw command guidance and handling procedure. |
…harp) Aaron 2026-04-23 directive: > lets try to reduce the number of class and thing we call servce titan > or this will be confusing in a Zeta repo. ... this is not a service > titan repo, it's an open source repo. Plus, 2026-04-23 follow-up on language priority: > c# is a more popular language than f# so it makes sense to start > with a factory c# demo anyways ## What renames - `samples/ServiceTitanFactoryApi.CSharp/` → `samples/FactoryDemo.Api.CSharp/` - Project name + csproj filename same rename - `RootNamespace` `Zeta.Samples.ServiceTitanFactoryApi` → `Zeta.Samples.FactoryDemo.Api` - `namespace` declarations in .cs files match - Zeta.sln project entry updated - README rewritten to generic framing (C# is the popular .NET language; demo starts there; F# stays reference) - Root endpoint name field `"ServiceTitan factory-demo API (C#)"` → `"Factory-demo API (C#)"` - All doc cross-references updated to new path names Build: 0 Warning(s), 0 Error(s) with the full SonarAnalyzer + Meziantou + Microsoft .NET Analyzers pack. Behaviour unchanged — same 9 endpoints, same JSON shapes, same seed. Memory rule: `memory/feedback_open_source_repo_demos_stay_generic_not_company_specific_2026_04_23.md` captures the positioning directive in durable form so future agents don't re-introduce company-specific names. Sibling renames land in separate PRs / branches: - F# API sibling (currently PR #146 / ServiceTitanFactoryApi) - DB scaffold (PR #145 / ServiceTitanFactoryDemo) - CRM kernel sample (PR #141 / ServiceTitanCrm) - CRM-UI scope doc (PR #144 / docs/plans/servicetitan-crm-ui-scope.md) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f78f3ed680
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e0280afa65
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
e0280af to
a5119f6
Compare
|
Addressed 6 findings scoped to this PR (mine):
Not addressed in this PR (stacked content from pre-session PRs):
The Not self-resolving conversations — human maintainer's move on review. 🤖 Generated with Claude Code |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a5119f664f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Investigated markdownlint failures across 7 demo-cluster PRs (#141/#145/#146/#147/#142/#143/#144). Scoped back on per-PR fixes in favor of a higher-leverage next-tick move: either land #151 (gitignore drop/ on main) or fix the MD056 tick-history row on main — both would unblock the whole cluster in one move. No new PRs this tick. Honest partial-execution per the prefer-progress-over-quiet-close discipline — documenting the unblock-vector rather than pushing through with inefficient per-PR fixes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…sibling ServiceTitan uses C# for most of their backend with zero F#. Shipping a C# companion to the F# API (#146) so ST engineers evaluating the factory see code in the language they already read fluently. F# stays the reference — it's closer to math, theorems are easier to express — but factory output matches audience stack. ## What lands - `ServiceTitanFactoryApi.CSharp.csproj` — `Microsoft.NET.Sdk.Web`, nullable + implicit usings enabled, TreatWarningsAsErrors - `Customer.cs`, `Opportunity.cs`, `Activity.cs` — records, one per file (MA0048) - `Seed.cs` — deterministic in-memory seed, identical to F# Seed.fs: 20 customers, 30 opportunities, 33 activities, 2 intentional email collisions - `Program.cs` — 9 minimal-API endpoints, identical routes + JSON shapes to the F# sibling - `README.md` — parity guarantee, design notes, C# specifics ## Smoke-test parity (verified) ``` GET /api/pipeline/funnel [{"stage":"Lead","count":10,"totalCents":5400000}, ...5 stages] GET /api/pipeline/duplicates [{"email":"alice@acme.example","customerIds":[1,13]}, {"email":"bob@trades.example","customerIds":[5,19]}] GET /api/customers -> 20 customers ``` Same seed, same shapes, same numbers as the F# version (#146). Frontends switch between them without code changes. ## Analyzer discipline passes Build: 0 Warning(s), 0 Error(s) with the full SonarAnalyzer.CSharp + Meziantou.Analyzer + Microsoft .NET Analyzers pack active. The C# companion respects every rule the F# version's discipline already encodes implicitly — StringComparer.Ordinal for GroupBy, static-readonly for endpoint list, record-per-file, no-var-discarded. ## Discipline signal Fourth EXT commit of the session (CRM demo #141, CRM scenario tests #143, F# API #146, now this C# API). All on Aaron's priority #1. The live-lock audit's inaugural lesson prescribed "ship external- priority increments when smell fires" — four landed in one session. ## Factory-pitch moment This pair (F# + C# from the same spec, identical behaviour) is a concrete factory-capability signal. The software factory produces code in your stack, to your analyzer discipline, with parity across languages. The pitch isn't "pick our language"; it's "your language, enforced by our quality floor." ## What this does NOT do - Does NOT rewrite or deprecate the F# sibling — both live - Does NOT wire Postgres — same v0 scope - Does NOT leak Zeta / DBSP / retraction-native concepts to the ST-facing surface - Does NOT claim the C# version is the primary — F# is reference Composes with: - `samples/ServiceTitanFactoryApi/` (F# sibling) - `memory/project_zeta_f_sharp_reference_c_sharp_and_rust_future_servicetitan_uses_csharp_2026_04_23.md` - `memory/feedback_servicetitan_demo_sells_software_factory_not_zeta_database_2026_04_23.md` Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…harp) Aaron 2026-04-23 directive: > lets try to reduce the number of class and thing we call servce titan > or this will be confusing in a Zeta repo. ... this is not a service > titan repo, it's an open source repo. Plus, 2026-04-23 follow-up on language priority: > c# is a more popular language than f# so it makes sense to start > with a factory c# demo anyways ## What renames - `samples/ServiceTitanFactoryApi.CSharp/` → `samples/FactoryDemo.Api.CSharp/` - Project name + csproj filename same rename - `RootNamespace` `Zeta.Samples.ServiceTitanFactoryApi` → `Zeta.Samples.FactoryDemo.Api` - `namespace` declarations in .cs files match - Zeta.sln project entry updated - README rewritten to generic framing (C# is the popular .NET language; demo starts there; F# stays reference) - Root endpoint name field `"ServiceTitan factory-demo API (C#)"` → `"Factory-demo API (C#)"` - All doc cross-references updated to new path names Build: 0 Warning(s), 0 Error(s) with the full SonarAnalyzer + Meziantou + Microsoft .NET Analyzers pack. Behaviour unchanged — same 9 endpoints, same JSON shapes, same seed. Memory rule: `memory/feedback_open_source_repo_demos_stay_generic_not_company_specific_2026_04_23.md` captures the positioning directive in durable form so future agents don't re-introduce company-specific names. Sibling renames land in separate PRs / branches: - F# API sibling (currently PR #146 / ServiceTitanFactoryApi) - DB scaffold (PR #145 / ServiceTitanFactoryDemo) - CRM kernel sample (PR #141 / ServiceTitanCrm) - CRM-UI scope doc (PR #144 / docs/plans/servicetitan-crm-ui-scope.md) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…pendent) Minimal F# ASP.NET Core Web API exposing 9 endpoints over an in-memory seed that mirrors the planned FactoryDemo.Db shape. Stack-independent — any frontend choice (Blazor / React / Vue / curl) consumes the same JSON. Cherry-pick-fresh drain of PR #146: scope reduced to the 5 sample files + Zeta.sln project registration. Unrelated commits from the original branch (SignalQuality module, CrmScenarios tests, drop/ zone, live-lock audit, operator-input log, hygiene-history additions, Zeta.sln BOM) are dropped per reviewer scope-split request; they ship through their own PRs. Review threads on the dropped surfaces are resolved as "elided by scope reduction"; threads on retained surfaces are addressed inline (README pointers updated to reflect that FactoryDemo.Db and the CSharp sibling are planned follow-ups; root endpoint list is now complete; Seed.fs / Program.fs comments no longer reference non-existent paths). Build: dotnet build -c Release -> 0W/0E. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
a5119f6 to
7ed2241
Compare
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
| dotnet run --project "$PROJECT" -c Release --no-build --urls "$URL" \ | ||
| > /tmp/factory-demo-api-fsharp.log 2>&1 & | ||
| API_PID=$! |
There was a problem hiding this comment.
P2: The smoke test writes server output to a fixed path under /tmp. This can be clobbered by concurrent runs and is left behind after success. Prefer creating a unique temp file (e.g., via mktemp) and removing it in cleanup (or printing its location) to avoid collisions and stale logs.
| (`samples/FactoryDemo.Api.CSharp/`, sibling PR #147) is planned | ||
| with the same 9 endpoints, matching JSON shapes, and identical | ||
| seed. C# is the more popular .NET language, so it is the natural | ||
| primary demo path; F# stays the reference because F# looks | ||
| closer to math, which makes theorems over the algebra easier | ||
| to express. |
There was a problem hiding this comment.
P2: The README hard-codes a sibling PR number ("PR #147") for the planned C# companion sample. PR numbers are repo-state specific and will become stale over time; consider referencing only the planned directory/path (or an issue/ADR) without embedding a PR number in long-lived docs.
| (`samples/FactoryDemo.Api.CSharp/`, sibling PR #147) is planned | |
| with the same 9 endpoints, matching JSON shapes, and identical | |
| seed. C# is the more popular .NET language, so it is the natural | |
| primary demo path; F# stays the reference because F# looks | |
| closer to math, which makes theorems over the algebra easier | |
| to express. | |
| at `samples/FactoryDemo.Api.CSharp/` is planned with the same 9 | |
| endpoints, matching JSON shapes, and identical seed. C# is the | |
| more popular .NET language, so it is the natural primary demo | |
| path; F# stays the reference because F# looks closer to math, | |
| which makes theorems over the algebra easier to express. |
| # Dependencies on host: dotnet, curl, jq. A C# sibling API (sibling | ||
| # PR #147) will land its own parallel smoke-test so parity between | ||
| # the two APIs is ground-truth-testable. |
There was a problem hiding this comment.
P2: This comment embeds a specific PR number for the planned C# sibling smoke test. PR numbers tend to go stale in long-lived scripts/docs; consider removing the PR reference and keeping the note focused on parity with the companion sample.
| # Dependencies on host: dotnet, curl, jq. A C# sibling API (sibling | |
| # PR #147) will land its own parallel smoke-test so parity between | |
| # the two APIs is ground-truth-testable. | |
| # Dependencies on host: dotnet, curl, jq. A C# sibling API will land | |
| # its own parallel smoke-test so parity between the two APIs is | |
| # ground-truth-testable. |
| // Email collision #1: Alice Plumbing (1) and Aaron Smith (13) share alice@acme.example. | ||
| // Email collision #2: Bob HVAC (5) and Quincy Assistant (19) share bob@trades.example. | ||
| [ mk 1L "Alice Plumbing LLC" "alice@acme.example" "555-0101" "123 Elm St, Portland OR" | ||
| mk 2L "Benson Roofing" "benson@roof.example" "555-0102" "45 Oak Ave, Seattle WA" | ||
| mk 3L "Crystal Electric" "crystal@sparks.example" "555-0103" "9 Pine Rd, Boise ID" | ||
| mk 4L "Delta HVAC & Mechanical" "delta@hvac.example" "555-0104" "700 Main St, Spokane WA" | ||
| mk 5L "Bob HVAC Services" "bob@trades.example" "555-0105" "12 Bay Blvd, Tacoma WA" | ||
| mk 6L "Evergreen Landscaping" "info@evergreen.example" "555-0106" "88 Forest Ln, Eugene OR" | ||
| mk 7L "Fairbanks Plumbing" "contact@fairbanks.example" "555-0107" "5 River Rd, Anchorage AK" | ||
| mk 8L "Granite Pest Control" "hello@granite.example" "555-0108" "301 Stone Way, Boise ID" | ||
| mk 9L "Highland Roofing Co" "highland@roof.example" "555-0109" "22 Hill Dr, Bend OR" | ||
| mk 10L "Iron Tree Electric" "iron@tree.example" "555-0110" "17 Spruce St, Salem OR" | ||
| mk 11L "Jackson Pool Services" "jackson@pools.example" "555-0111" "600 Lake Rd, Reno NV" | ||
| mk 12L "Klein Garage Doors" "klein@doors.example" "555-0112" "44 4th Ave, Medford OR" | ||
| mk 13L "Aaron Smith (new contact)" "alice@acme.example" "555-0113" "123 Elm St, Portland OR" | ||
| mk 14L "Lakeview Solar" "lakeview@solar.example" "555-0114" "250 Shore Dr, Bellevue WA" | ||
| mk 15L "Mountain Well Drilling" "mountain@wells.example" "555-0115" "12 Ridge Rd, Coeur dAlene ID" | ||
| mk 16L "Nightingale Security" "ngale@secure.example" "555-0116" "88 Watch Way, Vancouver WA" | ||
| mk 17L "Oak Hill Septic" "oak@septic.example" "555-0117" "14 Rural Rt 3, Gresham OR" | ||
| mk 18L "Prairie Window Cleaning" "prairie@windows.example" "555-0118" "66 Glass Rd, Kennewick WA" | ||
| mk 19L "Quincy Assistant (Bob HVAC)" "bob@trades.example" "555-0119" "12 Bay Blvd, Tacoma WA" | ||
| mk 20L "Redwood Tree Service" "redwood@trees.example" "555-0120" "3 Canopy Ct, Hillsboro OR" ] |
There was a problem hiding this comment.
P0: This seed data includes a specific person name ("Aaron Smith") in both comments and customer display name. Repo convention forbids direct contributor names in code/docs (docs/AGENT-BEST-PRACTICES.md:284-292); even if intended as fictional, it is indistinguishable from maintainer attribution. Please rename this customer/comment to a role-based or clearly fictional/non-contributor identifier and keep the duplicate-email scenario intact.
| // Email collision #1: Alice Plumbing (1) and Aaron Smith (13) share alice@acme.example. | |
| // Email collision #2: Bob HVAC (5) and Quincy Assistant (19) share bob@trades.example. | |
| [ mk 1L "Alice Plumbing LLC" "alice@acme.example" "555-0101" "123 Elm St, Portland OR" | |
| mk 2L "Benson Roofing" "benson@roof.example" "555-0102" "45 Oak Ave, Seattle WA" | |
| mk 3L "Crystal Electric" "crystal@sparks.example" "555-0103" "9 Pine Rd, Boise ID" | |
| mk 4L "Delta HVAC & Mechanical" "delta@hvac.example" "555-0104" "700 Main St, Spokane WA" | |
| mk 5L "Bob HVAC Services" "bob@trades.example" "555-0105" "12 Bay Blvd, Tacoma WA" | |
| mk 6L "Evergreen Landscaping" "info@evergreen.example" "555-0106" "88 Forest Ln, Eugene OR" | |
| mk 7L "Fairbanks Plumbing" "contact@fairbanks.example" "555-0107" "5 River Rd, Anchorage AK" | |
| mk 8L "Granite Pest Control" "hello@granite.example" "555-0108" "301 Stone Way, Boise ID" | |
| mk 9L "Highland Roofing Co" "highland@roof.example" "555-0109" "22 Hill Dr, Bend OR" | |
| mk 10L "Iron Tree Electric" "iron@tree.example" "555-0110" "17 Spruce St, Salem OR" | |
| mk 11L "Jackson Pool Services" "jackson@pools.example" "555-0111" "600 Lake Rd, Reno NV" | |
| mk 12L "Klein Garage Doors" "klein@doors.example" "555-0112" "44 4th Ave, Medford OR" | |
| mk 13L "Aaron Smith (new contact)" "alice@acme.example" "555-0113" "123 Elm St, Portland OR" | |
| mk 14L "Lakeview Solar" "lakeview@solar.example" "555-0114" "250 Shore Dr, Bellevue WA" | |
| mk 15L "Mountain Well Drilling" "mountain@wells.example" "555-0115" "12 Ridge Rd, Coeur dAlene ID" | |
| mk 16L "Nightingale Security" "ngale@secure.example" "555-0116" "88 Watch Way, Vancouver WA" | |
| mk 17L "Oak Hill Septic" "oak@septic.example" "555-0117" "14 Rural Rt 3, Gresham OR" | |
| mk 18L "Prairie Window Cleaning" "prairie@windows.example" "555-0118" "66 Glass Rd, Kennewick WA" | |
| mk 19L "Quincy Assistant (Bob HVAC)" "bob@trades.example" "555-0119" "12 Bay Blvd, Tacoma WA" | |
| mk 20L "Redwood Tree Service" "redwood@trees.example" "555-0120" "3 Canopy Ct, Hillsboro OR" ] | |
| // Email collision #1: Alice Plumbing (1) and Alice Plumbing Intake Contact (13) share alice@acme.example. | |
| // Email collision #2: Bob HVAC (5) and Quincy Assistant (19) share bob@trades.example. | |
| [ mk 1L "Alice Plumbing LLC" "alice@acme.example" "555-0101" "123 Elm St, Portland OR" | |
| mk 2L "Benson Roofing" "benson@roof.example" "555-0102" "45 Oak Ave, Seattle WA" | |
| mk 3L "Crystal Electric" "crystal@sparks.example" "555-0103" "9 Pine Rd, Boise ID" | |
| mk 4L "Delta HVAC & Mechanical" "delta@hvac.example" "555-0104" "700 Main St, Spokane WA" | |
| mk 5L "Bob HVAC Services" "bob@trades.example" "555-0105" "12 Bay Blvd, Tacoma WA" | |
| mk 6L "Evergreen Landscaping" "info@evergreen.example" "555-0106" "88 Forest Ln, Eugene OR" | |
| mk 7L "Fairbanks Plumbing" "contact@fairbanks.example" "555-0107" "5 River Rd, Anchorage AK" | |
| mk 8L "Granite Pest Control" "hello@granite.example" "555-0108" "301 Stone Way, Boise ID" | |
| mk 9L "Highland Roofing Co" "highland@roof.example" "555-0109" "22 Hill Dr, Bend OR" | |
| mk 10L "Iron Tree Electric" "iron@tree.example" "555-0110" "17 Spruce St, Salem OR" | |
| mk 11L "Jackson Pool Services" "jackson@pools.example" "555-0111" "600 Lake Rd, Reno NV" | |
| mk 12L "Klein Garage Doors" "klein@doors.example" "555-0112" "44 4th Ave, Medford OR" | |
| mk 13L "Alice Plumbing Intake Contact" "alice@acme.example" "555-0113" "123 Elm St, Portland OR" | |
| mk 14L "Lakeview Solar" "lakeview@solar.example" "555-0114" "250 Shore Dr, Bellevue WA" | |
| mk 15L "Mountain Well Drilling" "mountain@wells.example" "555-0115" "12 Ridge Rd, Coeur dAlene ID" | |
| mk 16L "Nightingale Security" "ngale@secure.example" "555-0116" "88 Watch Way, Vancouver WA" | |
| mk 17L "Oak Hill Septic" "oak@septic.example" "555-0117" "14 Rural Rt 3, Gresham OR" | |
| mk 18L "Prairie Window Cleaning" "prairie@windows.example" "555-0118" "66 Glass Rd, Kennewick WA" | |
| mk 19L "Quincy Assistant (Bob HVAC)" "bob@trades.example" "555-0119" "12 Bay Blvd, Tacoma WA" | |
| mk 20L "Redwood Tree Service" "redwood@trees.example" "555-0120" "3 Canopy Ct, Hillsboro OR" ] |
| app.MapGet("/", Func<_>(fun () -> | ||
| {| name = "Factory-demo API (F#)" | ||
| version = "0.0.1" | ||
| endpoints = | ||
| [ "/" | ||
| "/api/customers" | ||
| "/api/customers/{id}" | ||
| "/api/customers/{id}/activities" | ||
| "/api/opportunities" | ||
| "/api/opportunities/{id}" | ||
| "/api/activities" | ||
| "/api/pipeline/funnel" | ||
| "/api/pipeline/duplicates" ] |} :> obj)) |
There was a problem hiding this comment.
P1: JSON property casing is inconsistent across endpoints: the root endpoint returns lowercase fields (name/version/endpoints) while the record/anonymous-record responses from other endpoints will serialize as PascalCase by default. This creates a confusing contract for API consumers. Consider either configuring System.Text.Json to enforce a single naming policy (e.g., camelCase) for all responses or aligning the root payload field names with the rest of the API.
| # Pick a high random port to avoid clashes with other dev services. | ||
| PORT=$(( 5100 + RANDOM % 400 )) | ||
| URL="http://localhost:${PORT}" | ||
|
|
||
| echo "Building API..." | ||
| dotnet build "$PROJECT" -c Release --nologo -v quiet >/dev/null | ||
| echo "Starting API on ${URL}..." | ||
|
|
||
| dotnet run --project "$PROJECT" -c Release --no-build --urls "$URL" \ | ||
| > /tmp/factory-demo-api-fsharp.log 2>&1 & | ||
| API_PID=$! | ||
|
|
||
| cleanup() { | ||
| kill "$API_PID" 2>/dev/null || true | ||
| wait "$API_PID" 2>/dev/null || true | ||
| } | ||
| trap cleanup EXIT | ||
|
|
||
| for _ in {1..20}; do | ||
| if curl -sf "${URL}/" >/dev/null 2>&1; then | ||
| break | ||
| fi | ||
| sleep 0.5 | ||
| done | ||
|
|
||
| if ! curl -sf "${URL}/" >/dev/null 2>&1; then | ||
| echo "API did not come up within budget. Log:" >&2 | ||
| cat /tmp/factory-demo-api-fsharp.log >&2 |
There was a problem hiding this comment.
P1: The script claims to start on a "random free port", but it only picks a random port in a range and does not verify it is actually free. This can make the smoke test flaky if that port is already in use. Consider probing for a free port (or retrying on failure) instead of assuming the chosen port is available.
| # Pick a high random port to avoid clashes with other dev services. | |
| PORT=$(( 5100 + RANDOM % 400 )) | |
| URL="http://localhost:${PORT}" | |
| echo "Building API..." | |
| dotnet build "$PROJECT" -c Release --nologo -v quiet >/dev/null | |
| echo "Starting API on ${URL}..." | |
| dotnet run --project "$PROJECT" -c Release --no-build --urls "$URL" \ | |
| > /tmp/factory-demo-api-fsharp.log 2>&1 & | |
| API_PID=$! | |
| cleanup() { | |
| kill "$API_PID" 2>/dev/null || true | |
| wait "$API_PID" 2>/dev/null || true | |
| } | |
| trap cleanup EXIT | |
| for _ in {1..20}; do | |
| if curl -sf "${URL}/" >/dev/null 2>&1; then | |
| break | |
| fi | |
| sleep 0.5 | |
| done | |
| if ! curl -sf "${URL}/" >/dev/null 2>&1; then | |
| echo "API did not come up within budget. Log:" >&2 | |
| cat /tmp/factory-demo-api-fsharp.log >&2 | |
| API_LOG="/tmp/factory-demo-api-fsharp.log" | |
| API_PID="" | |
| PORT="" | |
| URL="" | |
| cleanup() { | |
| if [ -n "${API_PID}" ]; then | |
| kill "$API_PID" 2>/dev/null || true | |
| wait "$API_PID" 2>/dev/null || true | |
| fi | |
| } | |
| trap cleanup EXIT | |
| start_api() { | |
| local attempt | |
| local probe | |
| for attempt in {1..10}; do | |
| PORT=$(( 5100 + RANDOM % 400 )) | |
| URL="http://localhost:${PORT}" | |
| echo "Starting API on ${URL} (attempt ${attempt}/10)..." | |
| : > "$API_LOG" | |
| dotnet run --project "$PROJECT" -c Release --no-build --urls "$URL" \ | |
| > "$API_LOG" 2>&1 & | |
| API_PID=$! | |
| for probe in {1..20}; do | |
| if curl -sf "${URL}/" >/dev/null 2>&1; then | |
| return 0 | |
| fi | |
| if ! kill -0 "$API_PID" 2>/dev/null; then | |
| break | |
| fi | |
| sleep 0.5 | |
| done | |
| cleanup | |
| API_PID="" | |
| done | |
| return 1 | |
| } | |
| echo "Building API..." | |
| dotnet build "$PROJECT" -c Release --nologo -v quiet >/dev/null | |
| if ! start_api; then | |
| echo "API did not come up within budget. Log:" >&2 | |
| cat "$API_LOG" >&2 |
…sibling ServiceTitan uses C# for most of their backend with zero F#. Shipping a C# companion to the F# API (#146) so ST engineers evaluating the factory see code in the language they already read fluently. F# stays the reference — it's closer to math, theorems are easier to express — but factory output matches audience stack. ## What lands - `ServiceTitanFactoryApi.CSharp.csproj` — `Microsoft.NET.Sdk.Web`, nullable + implicit usings enabled, TreatWarningsAsErrors - `Customer.cs`, `Opportunity.cs`, `Activity.cs` — records, one per file (MA0048) - `Seed.cs` — deterministic in-memory seed, identical to F# Seed.fs: 20 customers, 30 opportunities, 33 activities, 2 intentional email collisions - `Program.cs` — 9 minimal-API endpoints, identical routes + JSON shapes to the F# sibling - `README.md` — parity guarantee, design notes, C# specifics ## Smoke-test parity (verified) ``` GET /api/pipeline/funnel [{"stage":"Lead","count":10,"totalCents":5400000}, ...5 stages] GET /api/pipeline/duplicates [{"email":"alice@acme.example","customerIds":[1,13]}, {"email":"bob@trades.example","customerIds":[5,19]}] GET /api/customers -> 20 customers ``` Same seed, same shapes, same numbers as the F# version (#146). Frontends switch between them without code changes. ## Analyzer discipline passes Build: 0 Warning(s), 0 Error(s) with the full SonarAnalyzer.CSharp + Meziantou.Analyzer + Microsoft .NET Analyzers pack active. The C# companion respects every rule the F# version's discipline already encodes implicitly — StringComparer.Ordinal for GroupBy, static-readonly for endpoint list, record-per-file, no-var-discarded. ## Discipline signal Fourth EXT commit of the session (CRM demo #141, CRM scenario tests #143, F# API #146, now this C# API). All on Aaron's priority #1. The live-lock audit's inaugural lesson prescribed "ship external- priority increments when smell fires" — four landed in one session. ## Factory-pitch moment This pair (F# + C# from the same spec, identical behaviour) is a concrete factory-capability signal. The software factory produces code in your stack, to your analyzer discipline, with parity across languages. The pitch isn't "pick our language"; it's "your language, enforced by our quality floor." ## What this does NOT do - Does NOT rewrite or deprecate the F# sibling — both live - Does NOT wire Postgres — same v0 scope - Does NOT leak Zeta / DBSP / retraction-native concepts to the ST-facing surface - Does NOT claim the C# version is the primary — F# is reference Composes with: - `samples/ServiceTitanFactoryApi/` (F# sibling) - `memory/project_zeta_f_sharp_reference_c_sharp_and_rust_future_servicetitan_uses_csharp_2026_04_23.md` - `memory/feedback_servicetitan_demo_sells_software_factory_not_zeta_database_2026_04_23.md` Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…harp) Aaron 2026-04-23 directive: > lets try to reduce the number of class and thing we call servce titan > or this will be confusing in a Zeta repo. ... this is not a service > titan repo, it's an open source repo. Plus, 2026-04-23 follow-up on language priority: > c# is a more popular language than f# so it makes sense to start > with a factory c# demo anyways ## What renames - `samples/ServiceTitanFactoryApi.CSharp/` → `samples/FactoryDemo.Api.CSharp/` - Project name + csproj filename same rename - `RootNamespace` `Zeta.Samples.ServiceTitanFactoryApi` → `Zeta.Samples.FactoryDemo.Api` - `namespace` declarations in .cs files match - Zeta.sln project entry updated - README rewritten to generic framing (C# is the popular .NET language; demo starts there; F# stays reference) - Root endpoint name field `"ServiceTitan factory-demo API (C#)"` → `"Factory-demo API (C#)"` - All doc cross-references updated to new path names Build: 0 Warning(s), 0 Error(s) with the full SonarAnalyzer + Meziantou + Microsoft .NET Analyzers pack. Behaviour unchanged — same 9 endpoints, same JSON shapes, same seed. Memory rule: `memory/feedback_open_source_repo_demos_stay_generic_not_company_specific_2026_04_23.md` captures the positioning directive in durable form so future agents don't re-introduce company-specific names. Sibling renames land in separate PRs / branches: - F# API sibling (currently PR #146 / ServiceTitanFactoryApi) - DB scaffold (PR #145 / ServiceTitanFactoryDemo) - CRM kernel sample (PR #141 / ServiceTitanCrm) - CRM-UI scope doc (PR #144 / docs/plans/servicetitan-crm-ui-scope.md) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…dpoint-count - tests/Tests.FSharp/Algebra/SignalQuality.Tests.fs: test asserted 1.0 for compressionRatio on empty input, but the fix in 16ad746 changed the convention to 0.0 (neutral = clean, not maximally suspicious). Updated the test expectation + name + comment to match the current code. - samples/FactoryDemo.Api.CSharp/smoke-test.sh: root.endpoints length expectation was 5; Program.cs now advertises 8 routes in the index (post 16ad746 expansion). Corrected the smoke-test assertion. Rebased onto origin/main (which advanced via #146 FactoryDemo.Api.FSharp merge); Zeta.sln conflicts resolved by keeping both FactoryDemo.Api.FSharp and the ServiceTitanCrm/samples solution-folder additions. Build gate: 0 Warning(s) / 0 Error(s) in Release.
Addresses five unresolved review threads: - drop/README.md: sweep name attribution to "the human maintainer" role-ref (BP-name-attribution). - samples/FactoryDemo.Api.CSharp/Program.cs: fix endpoint comment "9 concrete endpoints" → "8 API endpoints besides `/`" (array has 8; root excluded). - samples/FactoryDemo.Api.CSharp/smoke-test.sh: per-run log via mktemp (collision-safe + non-/tmp-host-safe); print path on failure + success. - samples/ServiceTitanFactoryApi/: delete stale F# sibling dir (PR #146 already landed FactoryDemo.Api.FSharp on main with identical code); drop duplicate sln Project block + config duplicates; fix CSharp refs to point at the surviving FactoryDemo.Api.FSharp/. Fifth thread (SignalQuality scope-creep) is judgment — branch history is deep; splitting now adds more churn than value. Replying with backlog-and-resolve per three-outcome.
…sibling ServiceTitan uses C# for most of their backend with zero F#. Shipping a C# companion to the F# API (#146) so ST engineers evaluating the factory see code in the language they already read fluently. F# stays the reference — it's closer to math, theorems are easier to express — but factory output matches audience stack. ## What lands - `ServiceTitanFactoryApi.CSharp.csproj` — `Microsoft.NET.Sdk.Web`, nullable + implicit usings enabled, TreatWarningsAsErrors - `Customer.cs`, `Opportunity.cs`, `Activity.cs` — records, one per file (MA0048) - `Seed.cs` — deterministic in-memory seed, identical to F# Seed.fs: 20 customers, 30 opportunities, 33 activities, 2 intentional email collisions - `Program.cs` — 9 minimal-API endpoints, identical routes + JSON shapes to the F# sibling - `README.md` — parity guarantee, design notes, C# specifics ## Smoke-test parity (verified) ``` GET /api/pipeline/funnel [{"stage":"Lead","count":10,"totalCents":5400000}, ...5 stages] GET /api/pipeline/duplicates [{"email":"alice@acme.example","customerIds":[1,13]}, {"email":"bob@trades.example","customerIds":[5,19]}] GET /api/customers -> 20 customers ``` Same seed, same shapes, same numbers as the F# version (#146). Frontends switch between them without code changes. ## Analyzer discipline passes Build: 0 Warning(s), 0 Error(s) with the full SonarAnalyzer.CSharp + Meziantou.Analyzer + Microsoft .NET Analyzers pack active. The C# companion respects every rule the F# version's discipline already encodes implicitly — StringComparer.Ordinal for GroupBy, static-readonly for endpoint list, record-per-file, no-var-discarded. ## Discipline signal Fourth EXT commit of the session (CRM demo #141, CRM scenario tests #143, F# API #146, now this C# API). All on Aaron's priority #1. The live-lock audit's inaugural lesson prescribed "ship external- priority increments when smell fires" — four landed in one session. ## Factory-pitch moment This pair (F# + C# from the same spec, identical behaviour) is a concrete factory-capability signal. The software factory produces code in your stack, to your analyzer discipline, with parity across languages. The pitch isn't "pick our language"; it's "your language, enforced by our quality floor." ## What this does NOT do - Does NOT rewrite or deprecate the F# sibling — both live - Does NOT wire Postgres — same v0 scope - Does NOT leak Zeta / DBSP / retraction-native concepts to the ST-facing surface - Does NOT claim the C# version is the primary — F# is reference Composes with: - `samples/ServiceTitanFactoryApi/` (F# sibling) - `memory/project_zeta_f_sharp_reference_c_sharp_and_rust_future_servicetitan_uses_csharp_2026_04_23.md` - `memory/feedback_servicetitan_demo_sells_software_factory_not_zeta_database_2026_04_23.md` Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…harp) Aaron 2026-04-23 directive: > lets try to reduce the number of class and thing we call servce titan > or this will be confusing in a Zeta repo. ... this is not a service > titan repo, it's an open source repo. Plus, 2026-04-23 follow-up on language priority: > c# is a more popular language than f# so it makes sense to start > with a factory c# demo anyways ## What renames - `samples/ServiceTitanFactoryApi.CSharp/` → `samples/FactoryDemo.Api.CSharp/` - Project name + csproj filename same rename - `RootNamespace` `Zeta.Samples.ServiceTitanFactoryApi` → `Zeta.Samples.FactoryDemo.Api` - `namespace` declarations in .cs files match - Zeta.sln project entry updated - README rewritten to generic framing (C# is the popular .NET language; demo starts there; F# stays reference) - Root endpoint name field `"ServiceTitan factory-demo API (C#)"` → `"Factory-demo API (C#)"` - All doc cross-references updated to new path names Build: 0 Warning(s), 0 Error(s) with the full SonarAnalyzer + Meziantou + Microsoft .NET Analyzers pack. Behaviour unchanged — same 9 endpoints, same JSON shapes, same seed. Memory rule: `memory/feedback_open_source_repo_demos_stay_generic_not_company_specific_2026_04_23.md` captures the positioning directive in durable form so future agents don't re-introduce company-specific names. Sibling renames land in separate PRs / branches: - F# API sibling (currently PR #146 / ServiceTitanFactoryApi) - DB scaffold (PR #145 / ServiceTitanFactoryDemo) - CRM kernel sample (PR #141 / ServiceTitanCrm) - CRM-UI scope doc (PR #144 / docs/plans/servicetitan-crm-ui-scope.md) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…dpoint-count - tests/Tests.FSharp/Algebra/SignalQuality.Tests.fs: test asserted 1.0 for compressionRatio on empty input, but the fix in 16ad746 changed the convention to 0.0 (neutral = clean, not maximally suspicious). Updated the test expectation + name + comment to match the current code. - samples/FactoryDemo.Api.CSharp/smoke-test.sh: root.endpoints length expectation was 5; Program.cs now advertises 8 routes in the index (post 16ad746 expansion). Corrected the smoke-test assertion. Rebased onto origin/main (which advanced via #146 FactoryDemo.Api.FSharp merge); Zeta.sln conflicts resolved by keeping both FactoryDemo.Api.FSharp and the ServiceTitanCrm/samples solution-folder additions. Build gate: 0 Warning(s) / 0 Error(s) in Release.
Addresses five unresolved review threads: - drop/README.md: sweep name attribution to "the human maintainer" role-ref (BP-name-attribution). - samples/FactoryDemo.Api.CSharp/Program.cs: fix endpoint comment "9 concrete endpoints" → "8 API endpoints besides `/`" (array has 8; root excluded). - samples/FactoryDemo.Api.CSharp/smoke-test.sh: per-run log via mktemp (collision-safe + non-/tmp-host-safe); print path on failure + success. - samples/ServiceTitanFactoryApi/: delete stale F# sibling dir (PR #146 already landed FactoryDemo.Api.FSharp on main with identical code); drop duplicate sln Project block + config duplicates; fix CSharp refs to point at the surviving FactoryDemo.Api.FSharp/. Fifth thread (SignalQuality scope-creep) is judgment — branch history is deep; splitting now adds more churn than value. Replying with backlog-and-resolve per three-outcome.
…sibling (#147) * Live-lock audit history: inaugural lesson integrated — prevention discipline for next time Aaron 2026-04-23: > if you want to beat ARC3 and do better than humans at uptime and > other DORA metrics then your live-lock smell and the decisions you > make to prevent live locks in the future based on pass lessons, the > ability to integrate previous lessions and not forget is ging to be > key. Lesson-permanence is the factory's competitive differentiator. Detection (audit script) is table stakes. Integration — recording the lesson, consulting it forward, preventing re-occurrence — is the product. ## What lands - New "Lessons integrated" section in `docs/hygiene-history/live-lock-audit-history.md` - Inaugural lesson from tonight's smell-firing event, structured as signature / mechanism / prevention with 4 concrete prevention decisions: 1. External-priority stack is authoritative; agent reorders only internal priorities 2. Live-lock audit at round-close is a gate-not-a-report 3. Speculative-work permit requires external-ratio check first 4. Tick-history rows are explicitly NOT external work; pair INTL with EXT when the smell is near firing - Open carry-forward named: round-close-ladder wiring is a P1 follow-up (BACKLOG row already filed earlier this session) ## Discipline Every future smell firing files a lesson to this same section. `memory/feedback_lesson_permanence_is_how_we_beat_arc3_and_dora_2026_04_23.md` captures the full rule: detection is not enough, integration is the product, lessons are consulted BEFORE taking actions that match known failure-mode signatures, memory persists across sessions. The pattern extends beyond live-lock: other detection mechanisms (SignalQuality firing, Amara-oracle rejecting, drift-tick exceeding threshold, OpenSpec Viktor failing rebuild-from-spec) should file lessons to their respective hygiene-history files. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * samples: ServiceTitan factory-demo JSON API (v0, in-memory, stack-independent) Minimal F# ASP.NET Core Web API serving CRM seed data as JSON. Any frontend choice (Blazor / React / Vue / curl) consumes the same endpoints. Ships now so the backend is not on the critical path when Aaron picks the frontend stack. ## What lands - `samples/ServiceTitanFactoryApi/ServiceTitanFactoryApi.fsproj` using `Microsoft.NET.Sdk.Web`; only explicit package ref is `FSharp.Core` (ASP.NET Core comes via framework reference, no Directory.Packages.props edit needed) - `Seed.fs` — in-memory seed mirroring `ServiceTitanFactoryDemo/seed-data.sql`: 20 customers, 30 opportunities (5 stages), 33 activities, 2 intentional email collisions. Deterministic fixed clock at 2026-04-23 00:00 UTC. - `Program.fs` — minimal F# API with 9 endpoints: customers (list/detail), opportunities (list/detail), activities (list/per-customer), pipeline funnel (count + total-cents per stage), duplicates (customers sharing an email). - `README.md` — framing (software-factory demo, not database pitch), endpoint table, design notes, v1 roadmap. ## Smoke-test output (verified) ``` GET /api/pipeline/funnel [{"count":10,"stage":"Lead","totalCents":5400000}, {"count":6, "stage":"Qualified","totalCents":4220000}, {"count":6, "stage":"Proposal","totalCents":5720000}, {"count":6, "stage":"Won","totalCents":2670000}, {"count":2, "stage":"Lost","totalCents":490000}] GET /api/pipeline/duplicates [{"customerIds":[1,13],"email":"alice@acme.example"}, {"customerIds":[5,19],"email":"bob@trades.example"}] ``` Build: 0 Warning(s), 0 Error(s). `dotnet run` starts the API; curl confirms all endpoints respond correctly. ## Discipline signal This is the third EXT commit of the session (CRM demo sample #141, CRM scenario tests in #143, now this API). The live-lock audit's inaugural lesson explicitly prescribed shipping external-priority increments when the smell fires. Three landed this session, all on priority #1 (ServiceTitan + UI) — the factory is correctly response-pattern even before any of tonight's PRs merge to main. ## What this does NOT do - Does NOT wire Postgres — in-memory only for v0; Npgsql wiring is a follow-up PR once Aaron confirms the DB driver - Does NOT expose Zeta / DBSP / retraction-native language to the frontend — standard CRUD shape per the ServiceTitan positioning directive - Does NOT implement writes — v0 is read-only; POST/PUT/DELETE is a follow-up - Does NOT add auth — no authentication for v0 - Does NOT ship docker-compose — future PR bundles this API with Postgres in one command Composes with: - `samples/ServiceTitanFactoryDemo/` (SQL schema + seed) — sibling, same shapes; v1 wires this API to that schema - `docs/plans/servicetitan-crm-ui-scope.md` — build sequence step 1 (API skeleton) complete; step 2 (DB wiring) is next - `memory/feedback_servicetitan_demo_sells_software_factory_not_zeta_database_2026_04_23.md` - `memory/feedback_lesson_permanence_is_how_we_beat_arc3_and_dora_2026_04_23.md` Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * samples: ServiceTitan factory-demo C# companion API — parity with F# sibling ServiceTitan uses C# for most of their backend with zero F#. Shipping a C# companion to the F# API (#146) so ST engineers evaluating the factory see code in the language they already read fluently. F# stays the reference — it's closer to math, theorems are easier to express — but factory output matches audience stack. ## What lands - `ServiceTitanFactoryApi.CSharp.csproj` — `Microsoft.NET.Sdk.Web`, nullable + implicit usings enabled, TreatWarningsAsErrors - `Customer.cs`, `Opportunity.cs`, `Activity.cs` — records, one per file (MA0048) - `Seed.cs` — deterministic in-memory seed, identical to F# Seed.fs: 20 customers, 30 opportunities, 33 activities, 2 intentional email collisions - `Program.cs` — 9 minimal-API endpoints, identical routes + JSON shapes to the F# sibling - `README.md` — parity guarantee, design notes, C# specifics ## Smoke-test parity (verified) ``` GET /api/pipeline/funnel [{"stage":"Lead","count":10,"totalCents":5400000}, ...5 stages] GET /api/pipeline/duplicates [{"email":"alice@acme.example","customerIds":[1,13]}, {"email":"bob@trades.example","customerIds":[5,19]}] GET /api/customers -> 20 customers ``` Same seed, same shapes, same numbers as the F# version (#146). Frontends switch between them without code changes. ## Analyzer discipline passes Build: 0 Warning(s), 0 Error(s) with the full SonarAnalyzer.CSharp + Meziantou.Analyzer + Microsoft .NET Analyzers pack active. The C# companion respects every rule the F# version's discipline already encodes implicitly — StringComparer.Ordinal for GroupBy, static-readonly for endpoint list, record-per-file, no-var-discarded. ## Discipline signal Fourth EXT commit of the session (CRM demo #141, CRM scenario tests #143, F# API #146, now this C# API). All on Aaron's priority #1. The live-lock audit's inaugural lesson prescribed "ship external- priority increments when smell fires" — four landed in one session. ## Factory-pitch moment This pair (F# + C# from the same spec, identical behaviour) is a concrete factory-capability signal. The software factory produces code in your stack, to your analyzer discipline, with parity across languages. The pitch isn't "pick our language"; it's "your language, enforced by our quality floor." ## What this does NOT do - Does NOT rewrite or deprecate the F# sibling — both live - Does NOT wire Postgres — same v0 scope - Does NOT leak Zeta / DBSP / retraction-native concepts to the ST-facing surface - Does NOT claim the C# version is the primary — F# is reference Composes with: - `samples/ServiceTitanFactoryApi/` (F# sibling) - `memory/project_zeta_f_sharp_reference_c_sharp_and_rust_future_servicetitan_uses_csharp_2026_04_23.md` - `memory/feedback_servicetitan_demo_sells_software_factory_not_zeta_database_2026_04_23.md` Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * rename: generic FactoryDemo.Api.CSharp (was ServiceTitanFactoryApi.CSharp) Aaron 2026-04-23 directive: > lets try to reduce the number of class and thing we call servce titan > or this will be confusing in a Zeta repo. ... this is not a service > titan repo, it's an open source repo. Plus, 2026-04-23 follow-up on language priority: > c# is a more popular language than f# so it makes sense to start > with a factory c# demo anyways ## What renames - `samples/ServiceTitanFactoryApi.CSharp/` → `samples/FactoryDemo.Api.CSharp/` - Project name + csproj filename same rename - `RootNamespace` `Zeta.Samples.ServiceTitanFactoryApi` → `Zeta.Samples.FactoryDemo.Api` - `namespace` declarations in .cs files match - Zeta.sln project entry updated - README rewritten to generic framing (C# is the popular .NET language; demo starts there; F# stays reference) - Root endpoint name field `"ServiceTitan factory-demo API (C#)"` → `"Factory-demo API (C#)"` - All doc cross-references updated to new path names Build: 0 Warning(s), 0 Error(s) with the full SonarAnalyzer + Meziantou + Microsoft .NET Analyzers pack. Behaviour unchanged — same 9 endpoints, same JSON shapes, same seed. Memory rule: `memory/feedback_open_source_repo_demos_stay_generic_not_company_specific_2026_04_23.md` captures the positioning directive in durable form so future agents don't re-introduce company-specific names. Sibling renames land in separate PRs / branches: - F# API sibling (currently PR #146 / ServiceTitanFactoryApi) - DB scaffold (PR #145 / ServiceTitanFactoryDemo) - CRM kernel sample (PR #141 / ServiceTitanCrm) - CRM-UI scope doc (PR #144 / docs/plans/servicetitan-crm-ui-scope.md) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * FactoryDemo.Api.CSharp: smoke-test.sh — end-to-end endpoint + contract verification I chose to land this because the JSON-shape parity claim we make in the README ("byte-identical shapes between F# and C# versions") needs a machine-verifiable check. A smoke test on the C# side is the first half; the F# sibling gets the same pattern in a follow-up. Starts the API on a random port, waits up to 10s for readiness, then runs 19 checks against all 9 endpoints: - Root metadata: name, version, endpoints length - Collection lengths: customers (20), opportunities (30), activities (33) - Single-item lookup: customer #1 name, opportunity #1 stage - Per-customer activities: customer #1 has 4 - Pipeline funnel counts per stage: Lead 10, Qualified 6, Won 6, Lost 2 - Pipeline funnel totals in cents: Lead $54k, Won $26.7k - Duplicates: 2 pairs, (1,13) share alice@acme, (5,19) share bob@trades - 404 behaviour: missing customer returns 404 Shuts the API down cleanly on exit via trap + kill. ``` $ bash samples/FactoryDemo.Api.CSharp/smoke-test.sh Building API... Starting API on http://localhost:5235... Factory-demo C# API smoke test ============================== OK root.name contains 'Factory-demo' (true) OK root.version (0.0.1) OK root.endpoints length (5) OK /api/customers length (20) ... OK missing customer HTTP status (404) All checks passed. ``` dotnet, curl, jq — all standard dev tools. The demo does not ask for anything exotic. Matches the FactoryDemo.Db smoke-test.sh pattern on the sibling branch. - Random high port (5100-5499) instead of fixed — reduces collision with other dev services. - `curl -sf` for normal checks, `curl -o /dev/null -w "%{http_code}"` for the 404 case — the two paths have different error semantics so I use different tools for each. - Shape-level assertions against numeric counts rather than raw JSON diff — makes the test tolerant of property-ordering differences between serializers. The parity claim is about *shape*, not byte- identity, so this matches intent. - Trap + kill on EXIT — guarantees the API stops even on test failure or ctrl-C. No leaked background processes. - Does NOT test the F# sibling. Same-pattern smoke-test for FactoryDemo.Api.FSharp lands in its branch (or a follow-up PR on that branch). - Does NOT diff F# vs C# outputs directly. A cross-language parity-diff test composes better as a separate tool once both APIs have merged. - Does NOT wire to Postgres. In-memory seed only; docker-compose + DB wiring is a separate PR. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * samples+audit: PR #147 review-drain — sln BOM, signal-quality empty-case, audit fail-hard, endpoint lists Drains 14 unresolved review threads on PR #147 (FactoryDemo.Api.CSharp): - Zeta.sln: strip leading blank line so 'Microsoft Visual Studio Solution File' is the first line (threads #2 #3). - SignalQuality.fs: compressionRatio on empty input was 1.0, which composed as Quarantine via severityOfScore — flipped to 0.0 and added explicit empty-input Pass finding in compressionMeasure; also dropped unused System.Runtime.CompilerServices open (threads #4 #5). - live-lock-audit.sh: fail hard (exit 2) when origin/main is not resolvable so a missing-remote CI checkout can't silently report 'No commits found' -> healthy; switched --stat|awk file-list extraction to git diff-tree --name-only plumbing form (threads #1 #6). - ServiceTitanFactoryApi README + Seed.fs: remove dead memory/ and docs/plans/ links; replace Aaron's-name reference with 'human maintainer' role wording; drop non-existent sibling SQL-seed refs (threads #7 #8 #9). - FactoryDemo.Api.CSharp README + Program.cs + Seed.cs: fix dead refs to samples/FactoryDemo.Api.FSharp/ and samples/FactoryDemo.Db/ to point at the real F# sibling samples/ServiceTitanFactoryApi/ and to a BACKLOG row for the Postgres-backed follow-up (threads #11 #14). - Program.cs + Program.fs: root endpoint index now advertises all 9 routes including the parameterised {id} routes, matching the README tables (threads #12 #13). - Thread #10 (project naming 'ServiceTitanFactoryApi.CSharp' in PR description): resolved in-thread — code/namespace already consistent (Zeta.Samples.FactoryDemo.Api); fix is PR-description- only, not code. Build: dotnet build -c Release -> 0 Warning(s) 0 Error(s). * drain PR #147: post-rebase thread fixes — test-empty-ratio + smoke-endpoint-count - tests/Tests.FSharp/Algebra/SignalQuality.Tests.fs: test asserted 1.0 for compressionRatio on empty input, but the fix in 16ad746 changed the convention to 0.0 (neutral = clean, not maximally suspicious). Updated the test expectation + name + comment to match the current code. - samples/FactoryDemo.Api.CSharp/smoke-test.sh: root.endpoints length expectation was 5; Program.cs now advertises 8 routes in the index (post 16ad746 expansion). Corrected the smoke-test assertion. Rebased onto origin/main (which advanced via #146 FactoryDemo.Api.FSharp merge); Zeta.sln conflicts resolved by keeping both FactoryDemo.Api.FSharp and the ServiceTitanCrm/samples solution-folder additions. Build gate: 0 Warning(s) / 0 Error(s) in Release. * PR #147 review-drain — Copilot pass on b4f5a49 Addresses five unresolved review threads: - drop/README.md: sweep name attribution to "the human maintainer" role-ref (BP-name-attribution). - samples/FactoryDemo.Api.CSharp/Program.cs: fix endpoint comment "9 concrete endpoints" → "8 API endpoints besides `/`" (array has 8; root excluded). - samples/FactoryDemo.Api.CSharp/smoke-test.sh: per-run log via mktemp (collision-safe + non-/tmp-host-safe); print path on failure + success. - samples/ServiceTitanFactoryApi/: delete stale F# sibling dir (PR #146 already landed FactoryDemo.Api.FSharp on main with identical code); drop duplicate sln Project block + config duplicates; fix CSharp refs to point at the surviving FactoryDemo.Api.FSharp/. Fifth thread (SignalQuality scope-creep) is judgment — branch history is deep; splitting now adds more churn than value. Replying with backlog-and-resolve per three-outcome. * PR #147 review-drain — 7 threads (Copilot + Codex) Threads drained: - btw.md: name attribution -> "human maintainer" / "the maintainer" (Copilot P1, AGENT-BEST-PRACTICES.md:284-292) - live-lock-audit.sh: add --root to git diff-tree so root commit classifies correctly (Copilot P2) - FactoryDemo.Api.CSharp Program.cs: add "/" to endpoints list for F# parity; bump smoke-test length 8->9 (Copilot P1 + Codex P2, same fix) - FactoryDemo.Api.CSharp smoke-test.sh: reword mktemp comment to describe system temp dir accurately (Copilot P2) - ServiceTitanCrm -> FactoryDemo.Crm: rename dir, fsproj, module namespace, RootNamespace, sln entry, test doc-comment; drop stale ServiceTitanFactoryApi bin+obj (Copilot P1, memory/feedback_open_source_repo_demos_stay_generic_not_company_specific_2026_04_23.md:59-66) - SignalQuality.fs: compressionRatio + compressionMeasure short-circuit to 0.0 (Pass) below 64-byte threshold to avoid gzip-header-dominates Quarantine of legitimate short strings (Codex P1) Drain log: docs/pr-preservation/147-drain-log.md preserves each thread verbatim (git-native high-signal preservation). dotnet build -c Release: 0 Warning(s), 0 Error(s). * PR #147 review-drain second pass — 4 fix-inline + 3 scope-bleed - Seed.cs + Seed.fs: rename contact 13 'Aaron Smith' -> 'Acme Contact (new lead)' (Copilot P2 name-attribution, parity preserved across C# / F# siblings). - drop/README.md: correct 'only tracked file' wording to reflect the README.md + .gitignore two-sentinel design (Copilot P2). - tools/audit/live-lock-audit.sh: docstring attribution 'Aaron's ...' -> 'Human-maintainer ...' (Copilot P1); add '-m' plus 'sort -u' to 'git diff-tree' so merge commits bucket on their real files instead of mis-classifying as OTHR (Codex P1 — was skewing EXT/INTL/SPEC % and could disable the live-lock gate after a round of merges). - docs/pr-preservation/147-drain-log.md: append second-pass per-thread audit trail (git-native preservation). Three threads resolved as scope-bleed / already-addressed: operator- input-quality-log.md (file not in PR diff, landed via 204bbb6 on main), AUTONOMOUS-LOOP.md (file not in PR diff, zero Aaron on HEAD), Tests.FSharp.fsproj (both SignalQuality + CrmScenarios already listed at lines 26 and 49). Build: 0W/0E. Audit sanity: live-lock-audit.sh still healthy with merges now bucketed correctly. * fix: markdownlint MD001/MD022/MD032 on #147 drain-log (h3→h2 on Thread headers) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * drain: resolve 11 threads on #147 (mix FIX + BACKLOG + Otto-256 reject) Thread-by-thread outcomes across the 11 unresolved review threads on PR #147 (5 FIX, 2 BACKLOG, 2 Otto-256 REJECT, 2 already-addressed/stale): FIXES (code): - live-lock-audit.sh: replace `git show --stat` with explicit `git log -1 -m --first-parent --name-only` so merge commits classify against parent-1 only (the landing side). The prior `git show` form risked combined-diff semantics in some git versions; the explicit form is first-parent by construction (Codex P1). - SignalQuality.fs: restore `compressionMinInputBytes = 64` threshold (dropped by the f1dc2bb merge-conflict resolution) and mark it `private` so it is not part of the public API surface (Copilot). Short-circuits `compressionRatio` + `compressionMeasure` to 0.0 for sub-threshold inputs, avoiding spurious Quarantine on short legitimate strings. Evidence reports UTF-8 byte count (consistent with the threshold's units) instead of `text.Length` chars (Copilot). Adjusted the empty-string test to assert the new 0.0 neutral value. - smoke-test.sh: replace non-portable `mktemp -t <template>` with a pre-constructed absolute-path template rooted at `${TMPDIR:-/tmp}` where XXXXXX is the tail (BSD/macOS requires tail-XXXXXX; GNU accepts either). `.log` extension is appended via `mv` after creation so the single invocation is cross-platform (Copilot x2 — threads 4 + 10). - CrmScenarios.Tests.fs: update doc-comment `samples/FactoryDemo.Crm` -> `samples/CrmSample` to match the canonical sample path on main (Copilot). BACKLOG (deferred P2): - Smoke-test deterministic port allocation (Codex P2) — replace RANDOM-in-range with OS-assigned ephemeral port via `--urls http://127.0.0.1:0` and log-line parse. - FactoryDemo.Api.CSharp solution project-type GUID hygiene (Copilot) — align with modern SDK-style GUID used by other C# projects. OTTO-256 REJECT (history-file exemption): - docs/pr-preservation/147-drain-log.md (Copilot) and docs/hygiene-history/live-lock-audit-history.md (Copilot): both requested stripping first-name "Aaron" attributions. Declined per Otto-256 (2026-04-24) — history files exempt from the "no name attribution" rule; a P2 BACKLOG row already exists (`## P2 — FACTORY-HYGIENE — name-attribution policy clarification (history-file exemption)`) to codify this in AGENT-BEST-PRACTICES.md. ALREADY-ADDRESSED (stale reviewer context): - drop/README.md heading (Copilot): Copilot flagged "one tracked sentinel" but the current heading reads "two tracked sentinels" (fixed in a prior drain). Resolving as addressed. Build: `dotnet build -c Release` -> 0 Warning(s), 0 Error(s). Tests: `dotnet test --filter "FullyQualifiedName~SignalQuality"` -> 22/22 pass. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Summary
Minimal F# ASP.NET Core Web API serving CRM seed data as JSON. Any frontend choice consumes the same 9 endpoints. Build green, 0 warnings.
dotnet run+ curl confirmed all endpoints working.Verified output
Endpoints
/api/customers+/api/customers/{id}+/api/customers/{id}/activities/api/opportunities+/api/opportunities/{id}/api/activities/api/pipeline/funnel(count + total-cents per stage)/api/pipeline/duplicates(customers sharing an email)Why stack-independent
Aaron has not picked Blazor vs React vs other for the frontend. This API ships a JSON contract any of them can consume. Frontend-stack decision stays open.
Why this move
Third EXT commit of the session on priority #1. Live-lock audit fired earlier; inaugural lesson said "ship external-priority increments when smell fires." Three landed, all priority #1.
What this does NOT do
Test plan
dotnet build -c Release— 0 Warning(s), 0 Error(s)dotnet runstarts API on http://localhost:5099🤖 Generated with Claude Code