Skip to content

samples: FactoryDemo.Api.FSharp — JSON API (v0, in-memory, stack-independent)#146

Merged
AceHack merged 1 commit intomainfrom
feat/servicetitan-factory-demo-api
Apr 24, 2026
Merged

samples: FactoryDemo.Api.FSharp — JSON API (v0, in-memory, stack-independent)#146
AceHack merged 1 commit intomainfrom
feat/servicetitan-factory-demo-api

Conversation

@AceHack
Copy link
Copy Markdown
Member

@AceHack AceHack commented Apr 23, 2026

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

GET /api/pipeline/funnel      → [{"count":10,"stage":"Lead","totalCents":5400000}, ...5 stages]
GET /api/pipeline/duplicates  → 2 collisions ([1,13] share alice@acme; [5,19] share bob@trades)
GET /api/customers/1          → {"id":1,"name":"Alice Plumbing LLC",...}

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

  • No Postgres wiring — in-memory v0; Npgsql is a follow-up PR
  • No Zeta / DBSP / retraction-native language exposed to frontend
  • No writes (read-only v0), no auth, no docker-compose yet

Test plan

  • dotnet build -c Release — 0 Warning(s), 0 Error(s)
  • dotnet run starts API on http://localhost:5099
  • All 9 endpoints return expected JSON shapes
  • Frontend team (Aaron's call on stack) can consume the API

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings April 23, 2026 13:49
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread tools/audit/live-lock-audit.sh Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) and samples/ServiceTitanCrm (console demo) for CRM-shaped scenarios.
  • Add src/Core/SignalQuality.fs plus 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.

Comment thread samples/FactoryDemo.Api.FSharp/README.md
Comment thread samples/FactoryDemo.Api.FSharp/Seed.fs Outdated
Comment thread tests/Tests.FSharp/Operators/CrmScenarios.Tests.fs Outdated
Comment thread src/Core/SignalQuality.fs Outdated
Comment thread docs/operator-input-quality-log.md Outdated
Comment thread src/Core/SignalQuality.fs Outdated
Comment thread samples/FactoryDemo.Api.FSharp/README.md Outdated
Comment thread samples/ServiceTitanFactoryApi/README.md Outdated
AceHack added a commit that referenced this pull request Apr 23, 2026
…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>
@AceHack AceHack changed the title samples: ServiceTitan factory-demo JSON API (v0, in-memory, stack-independent) samples: FactoryDemo.Api.FSharp — JSON API (v0, in-memory, stack-independent) Apr 23, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread Zeta.sln Outdated
Copilot AI review requested due to automatic review settings April 23, 2026 14:22
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/Core/SignalQuality.fs Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 36 out of 38 changed files in this pull request and generated 7 comments.

Comment thread samples/FactoryDemo.Api.FSharp/README.md Outdated
Comment thread samples/FactoryDemo.Api.FSharp/Program.fs Outdated
Comment thread README.md Outdated
Comment thread samples/FactoryDemo.Api.FSharp/smoke-test.sh Outdated
Comment thread Zeta.sln Outdated
Comment thread docs/hygiene-history/live-lock-audit-history.md Outdated
Comment thread drop/README.md Outdated
@AceHack
Copy link
Copy Markdown
Member Author

AceHack commented Apr 23, 2026

Addressed 6 findings scoped to this PR (mine):

  1. Zeta.sln BOM (codex + Copilot L1) — stripped. dotnet sln list still parses.
  2. README.md L23 (Copilot, CrmKernel path) — actually C# companion path at samples/FactoryDemo.Api.CSharp/ — added "(lands in PR samples: FactoryDemo.Api.CSharp — JSON API companion, parity with F# sibling #147)" marker.
  3. README.md L82 (Copilot, anonymous record rendering) — replaced {{ Stage, Count, TotalCents }} (which renders as single-brace in markdown) with explicit F# syntax: {| Stage = ...; Count = ...; TotalCents = ... |}.
  4. Program.fs L48 (Copilot, incomplete endpoints list) — expanded the endpoints array from 5 to all 9 endpoints (customers + by-id + activities, opportunities + by-id, activities, funnel, duplicates). Build stays green.
  5. smoke-test.sh L79-82 (Copilot, casing comment inconsistency) — corrected the comment: F# anonymous-record fields declared lowercase emit lowercase JSON. Matches C# after System.Text.Json default camelCasing. Renamed the check label from root.Name to root.name to match.

Not addressed in this PR (stacked content from pre-session PRs):

The README.md L22 contributor-name finding is a valid callout on previously-landed content I updated this session; it's in a file touched by multiple recent PRs and belongs on its own targeted sweep rather than conflated with this one.

Not self-resolving conversations — human maintainer's move on review.

🤖 Generated with Claude Code

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread src/Core/SignalQuality.fs Outdated
AceHack added a commit that referenced this pull request Apr 23, 2026
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>
@AceHack AceHack enabled auto-merge (squash) April 24, 2026 14:55
AceHack added a commit that referenced this pull request Apr 24, 2026
…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>
AceHack added a commit that referenced this pull request Apr 24, 2026
…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>
Copilot AI review requested due to automatic review settings April 24, 2026 16:50
@AceHack AceHack force-pushed the feat/servicetitan-factory-demo-api branch from a5119f6 to 7ed2241 Compare April 24, 2026 16:50
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@AceHack AceHack merged commit 66b3b05 into main Apr 24, 2026
13 checks passed
@AceHack AceHack deleted the feat/servicetitan-factory-demo-api branch April 24, 2026 16:52
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 6 comments.

Comment on lines +36 to +38
dotnet run --project "$PROJECT" -c Release --no-build --urls "$URL" \
> /tmp/factory-demo-api-fsharp.log 2>&1 &
API_PID=$!
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +19 to +24
(`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.
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
(`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.

Copilot uses AI. Check for mistakes.
Comment on lines +12 to +14
# 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.
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
# 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.

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +70
// 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" ]
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
// 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" ]

Copilot uses AI. Check for mistakes.
Comment on lines +44 to +56
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))
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +28 to +55
# 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
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
# 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

Copilot uses AI. Check for mistakes.
AceHack added a commit that referenced this pull request Apr 24, 2026
…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>
AceHack added a commit that referenced this pull request Apr 24, 2026
…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>
AceHack added a commit that referenced this pull request Apr 24, 2026
…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.
AceHack added a commit that referenced this pull request Apr 24, 2026
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.
AceHack added a commit that referenced this pull request Apr 24, 2026
…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>
AceHack added a commit that referenced this pull request Apr 24, 2026
…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>
AceHack added a commit that referenced this pull request Apr 24, 2026
…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.
AceHack added a commit that referenced this pull request Apr 24, 2026
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.
AceHack added a commit that referenced this pull request Apr 24, 2026
…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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants