Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
44a6ed4
SCALE-SEAM (95) — element state: two read/write pairs, and why the ma…
claude Sep 4, 2026
121c095
SCALE-SEAM (96) — the as-built question's aggregate reader, and a wit…
claude Sep 4, 2026
2f09a5c
Merge already-squashed (95) branch history
claude Sep 4, 2026
0472f97
SCALE-SEAM (97) — the undo stack, and a destination that looked right…
claude Sep 4, 2026
66ab67a
Merge already-squashed (96) branch history
claude Sep 4, 2026
58525e1
Correct the undo/redo republish docs — all three sites, not just the …
claude Sep 4, 2026
75f8597
SCALE-SEAM (98) — detailing carriers, and a field map total over one …
claude Sep 4, 2026
f2926a5
Merge already-squashed (97) branch history
claude Sep 4, 2026
83e149e
SCALE-SEAM (99) — the content shelf, and a destination header that wa…
claude Sep 4, 2026
17103db
Merge already-squashed (98) branch history
claude Sep 4, 2026
2a8f46f
SCALE-SEAM (100) — the element-connection pair, and a destination nam…
claude Sep 4, 2026
6d68219
Merge already-squashed (99) branch history
claude Sep 4, 2026
370a1f8
Merge already-squashed (95)-(100) branch history
claude Sep 4, 2026
f5d6751
R22-ENTITLEMENT ⑤ — an agency review comment becomes an RFI somebody …
claude Sep 4, 2026
5c79d56
Merge branch history — reconcile after the (95)-(100) squashes
claude Sep 4, 2026
d826115
R22-ENTITLEMENT ⑤ review: promotion claims the comment atomically, an…
claude Sep 4, 2026
9f048eb
Merge branch history after the #434 squash — content already identical
claude Sep 4, 2026
c437a7f
R24-REPORTS-BY-MOMENT — a finished pack can be sent, not only downloaded
claude Sep 4, 2026
87cc0e8
Review round on #435: five findings, all verified real, all fixed
claude Sep 4, 2026
67dfc36
Second review round on #435: the port fix is the root cause my first …
claude Sep 4, 2026
eff9568
Merge after the #435 squash
claude Sep 4, 2026
0fadd1c
Only committed capital owns anything — cap table and waterfall
claude Sep 4, 2026
87f3a59
Merge after the #436 squash
claude Sep 4, 2026
2fa0692
SCALE-SEAM (101) — design-phase predicted performance, client.ts 642 …
claude Sep 4, 2026
276c4e5
Merge after the #437 squash
claude Sep 4, 2026
ea414ab
Portfolio risk heat map — R22-PIPELINE
claude Sep 4, 2026
9ba106e
Review round on #439 — deterministic tie-break, keyboard-operable rows
claude Sep 4, 2026
4ae3e28
Merge remote-tracking branch 'origin/main' into claude/codebase-audit…
claude Sep 4, 2026
27d6f40
Cross-project Gantt — R22-PIPELINE
claude Sep 4, 2026
3f07a03
Review round on #440 — reject normalised dates, require the id separa…
claude Sep 4, 2026
285f262
Merge remote-tracking branch 'origin/main' into claude/codebase-audit…
claude Sep 4, 2026
09a2446
Portfolio resourcing — R22-PIPELINE's last item
claude Sep 4, 2026
b614e0b
Merge remote-tracking branch 'origin/main' into claude/codebase-audit…
claude Sep 4, 2026
fadc11d
Roadmap truth pass + R39-DECOMP-VIEWER (17)
claude Sep 5, 2026
8c9314d
SCALE-SEAM (102) — counterparty risk, client.ts 603 -> 589
claude Sep 5, 2026
c7a58ae
Review fixes on #442, and a gate for the number that keeps drifting
claude Sep 5, 2026
a2b33eb
Merge remote-tracking branch 'origin/main' into claude/codebase-audit…
claude Sep 5, 2026
e1f1a05
SCALE-SEAM (103) — acceptance gates, client.ts 589 -> 572
claude Sep 5, 2026
9875dba
Merge remote-tracking branch 'origin/main' into claude/codebase-audit…
claude Sep 5, 2026
7e2f4fd
SCALE-SEAM (104) — coverage maps, client.ts 572 -> 554
claude Sep 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,45 @@ All notable changes to Massing. Releases are signed, auto-updating desktop build
(Windows / macOS / Linux); the updater always serves the latest. Format loosely follows
[Keep a Changelog](https://keepachangelog.com/).

## Unreleased — SCALE-SEAM (104): coverage maps

Extracts `spineTraceability` and `scopeRegister` from `client.ts` (**572 → 554**) into
`apps/web/src/api/coverageMaps.ts` — *how completely is this project's chain of records linked, and
exactly what is missing.*

**This is the affirmative half of a boundary (103) drew by exclusion.** `acceptanceGates.ts` took the
four methods that collapse the project to one accept/refuse verdict, and named `spineTraceability` as
the closest miss *precisely because* it maps completeness rather than deciding. Both members here
report proportion-complete across several link types — `specs_packaged_pct`, `packages_costed_pct`,
`sheets_specced_pct`, `spec_to_budget_pct`; `pct_quantified`, `pct_allocated`, `pct_scheduled` — plus
the individual records lacking the link: `specs_without_bid_package`, `bid_packages_without_cost_code`,
`sheets_without_spec`; `gap_items` with per-item `gaps[]` and `status: "complete" | "gap"`. **Neither
carries a verdict field.** Same test as (103), run for inclusion this time.

**Deriving the population required resolving named return types, and that is the lesson.** A scan of
method bodies for coverage vocabulary returned `scopeRegister`, `citedQuery` and `progressActuals` —
and **missed `spineTraceability`, the strongest member**, because its return is the named type
`SpineTraceability` and its body contains none of those words. That is the exact **mirror** of the
mistake (103) recorded, where a body scan *over*-counted by matching the doc comment of the next
method. *A textual scan of a typed language reads neither the comments nor the types correctly — it
finds candidates, it never counts them.*

**Exclusions.** `citedQuery` carries `coverage`, `uncited_claims` and `fully_cited`, but its product
is an *answer* whose sourcing those annotate — the caller renders a cited answer, not a worklist.
`masterBuilderBrief` is **the closest call in this slice**, closer than `spineTraceability` was to the
gates: it has `readiness_pct`, `ready_steps` and `gap_steps`. It stays because its product is a
narrative brief with a `reframe_prompt` and a `disclaimer`, where the percentages head a document
rather than being it — recorded rather than asserted, since a later reader may decide otherwise.
`progressActuals`'s `pct_complete` is physical progress against a planned quantity, banded
`ahead`/`on_track`/`behind`: variance over time, not completeness of linkage.

**A correction to a follow-up proposed in (103).** That PR said three consecutive slices leaving an
orphaned type import in `client.ts` was "a pattern that wants a gate rather than vigilance". **It
already has one.** `tsc` reports the orphan as `TS6196`, and it caught every instance — `EnergyResult`
(101), `PrequalScores` (102), `DiligenceReadiness` (103) and now `SpineTraceability` (104). None ever
reached a commit. Proposing a gate for a class the toolchain already gates is how a checklist grows
without getting safer; the follow-up is withdrawn.

## Unreleased — SCALE-SEAM (103): acceptance gates

Extracts `permitReadiness`, `diligenceReadiness`, `handoverAcceptance` and `validate` from
Expand Down
24 changes: 3 additions & 21 deletions apps/web/src/api/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { withRoutines } from "./routines";
import { withContracts } from "./contracts";
import { withCounterpartyRisk } from "./counterpartyRisk";
import { withAcceptanceGates } from "./acceptanceGates";
import { withCoverageMaps } from "./coverageMaps";
import { withDesignOptions } from "./designOptions";
import { withFinance } from "./finance";
import { withLibrary } from "./library";
Expand Down Expand Up @@ -67,13 +68,12 @@ import type {
DisciplineTree, ModulePin, RoomAllocation,
PropMapRule,
SpecManual, WorkItem, VitalsPayload,
MasterBuilderBrief,
SpineTraceability } from "./types";
MasterBuilderBrief } from "./types";


// Transport (baseUrl, token, json/_pdfPost/url/health) lives in HttpCore; ApiClient adds the typed
// domain methods below. Every `api.method()` call site is unchanged by the split.
export class ApiClient extends withAcceptanceGates(withCounterpartyRisk(withDesignPerformance(withDetailing(withAnnotate(withCreDeal(withClientPortal(withResilience(withResponsibility(withOperations(withAccounting(withDealMemory(withPdfTools(withCodeCheck(withSpecialty(withIds(withEvm(withRisk(withEntitlements(withPrecon(withAi(withTopics(withMep(withDocuments(withModels(withElements(withDrawingSheets(withDrawingSet(withMarkup(withSync(withConnections(withDocQa(withFinance(withContracts(withAuth(withProforma(withDesignOptions(withRoutines(withCost(withProcurement(withEstimate(withModules(withModel(withSchedule(withLibrary(withAssetRights(withAuthoring(HttpCore))))))))))))))))))))))))))))))))))))))))))))))) {
export class ApiClient extends withCoverageMaps(withAcceptanceGates(withCounterpartyRisk(withDesignPerformance(withDetailing(withAnnotate(withCreDeal(withClientPortal(withResilience(withResponsibility(withOperations(withAccounting(withDealMemory(withPdfTools(withCodeCheck(withSpecialty(withIds(withEvm(withRisk(withEntitlements(withPrecon(withAi(withTopics(withMep(withDocuments(withModels(withElements(withDrawingSheets(withDrawingSet(withMarkup(withSync(withConnections(withDocQa(withFinance(withContracts(withAuth(withProforma(withDesignOptions(withRoutines(withCost(withProcurement(withEstimate(withModules(withModel(withSchedule(withLibrary(withAssetRights(withAuthoring(HttpCore)))))))))))))))))))))))))))))))))))))))))))))))) {
/**
* R22-PHOTO-CV — attach a field photo to an element and get the server's read on it back.
*
Expand Down Expand Up @@ -273,20 +273,6 @@ export class ApiClient extends withAcceptanceGates(withCounterpartyRisk(withDesi
note: string;
}>(`/projects/${pid}/permits/timeline`, { method: "POST", body: JSON.stringify(body) });
}
scopeRegister(pid: string, body: {
scope_items: Record<string, unknown>[]; qto_lines?: Record<string, unknown>[]; activities?: Record<string, unknown>[];
}) {
type Item = {
id: string | null; name: string; cost_code: string | null; qty: number | null; value: number | null;
responsible: string | null; package: string | null; start: string | null; finish: string | null;
quantified: boolean; allocated: boolean; scheduled: boolean; gaps: string[]; status: "complete" | "gap";
};
return this.json<{
item_count: number; complete: number; with_gaps: number; pct_quantified: number; pct_allocated: number;
pct_scheduled: number; total_value: number; by_owner: { owner: string; value: number }[];
gap_items: Item[]; items: Item[]; note: string;
}>(`/projects/${pid}/scope/register`, { method: "POST", body: JSON.stringify(body) });
}
citedQuery(pid: string, query: string, property?: string, persona?: "exec" | "pm" | "field") {
type CitationRef = {
source_type: "ifc" | "doc" | "record" | "rule"; document_id: string | null; revision: string | null;
Expand Down Expand Up @@ -388,10 +374,6 @@ export class ApiClient extends withAcceptanceGates(withCounterpartyRisk(withDesi



/** Discipline Spine traceability: discipline → sheets → specs → bid packages → cost codes → budget. */
spineTraceability(pid: string) {
return this.json<SpineTraceability>(`/projects/${pid}/spine/traceability`);
}

// --- concept space programming: adjacency graph + massing hints ---------------
programSummary(pid: string) {
Expand Down
85 changes: 85 additions & 0 deletions apps/web/src/api/coverageMaps.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { HttpCore } from "./httpCore";
import type { SpineTraceability } from "./types";

/**
* Coverage maps — **how completely is this project's chain of records linked, and exactly what is
* missing.**
*
* SCALE-SEAM (104), and it is the affirmative half of a boundary (103) drew by exclusion.
* `acceptanceGates.ts` groups the four methods that collapse the project to one accept/refuse
* verdict. `spineTraceability` was named there as the closest miss and deliberately left behind,
* because it *maps completeness for a human* rather than deciding anything. This is that shape,
* given its home:
*
* | method | what it counts complete | the gaps it names |
* |---|---|---|
* | `spineTraceability` | `specs_packaged_pct`, `packages_costed_pct`, `sheets_specced_pct`, `spec_to_budget_pct` | `specs_without_bid_package[]`, `bid_packages_without_cost_code[]`, `sheets_without_spec[]` |
* | `scopeRegister` | `pct_quantified`, `pct_allocated`, `pct_scheduled` | `gap_items[]`, and per item `gaps: string[]` with `status: "complete" \| "gap"` |
*
* Both answer one question — *what proportion of these records carry the link they need, and which
* specific ones do not* — and a caller does the same thing with either: render a worklist of records
* to go and connect. **Neither returns a verdict field**, which is exactly what separates them from
* the acceptance gates, and it is the same test, run for inclusion this time rather than exclusion.
*
* ## Deriving the population needed named types resolved, and that is the lesson here
*
* A scan of method BODIES for coverage vocabulary (`gaps`, `coverage`, `pct_*`) returned
* `scopeRegister`, `citedQuery` and `progressActuals` — and **missed `spineTraceability`, the
* strongest member**, because its return is the named type `SpineTraceability` and its body
* therefore contains none of those words. Resolving the named return types out of `types.ts` is what
* found it.
*
* That is the mirror of the mistake (103) recorded. There, a body scan **over**-counted, matching the
* doc comment of the *next* method and reporting `collabSnapshot` as verdict-shaped. Here the same
* kind of scan **under**-counted, because a type alias hides the shape it names. *A textual scan of a
* typed language reads neither the comments nor the types correctly — it is a way of finding
* candidates, never a way of counting them.*
*
* ## What did NOT come
*
* **`citedQuery` stayed**, and it genuinely carries `coverage`, `uncited_claims` and `fully_cited`.
* Its principal product is an `answer` with `claims` and `conflicts`; the coverage fields annotate
* how well that answer is sourced. The caller renders a cited answer, not a worklist of records to
* link, so the shape matches while the question does not.
*
* **`masterBuilderBrief` stayed, and it is the closest call in this slice** — closer than
* `spineTraceability` was to the gates. It carries `readiness_pct`, `ready_steps`, `gap_steps` and a
* `steps[]` list, so it does count completeness and does enumerate gaps. It stays because its
* product is a *brief*: a narrative with a `reframe_prompt`, a `disclaimer` and guided steps to read,
* where the percentages are a header on a document rather than the document. Recorded rather than
* asserted, because a later reader may reasonably decide the other way.
*
* **`progressActuals` stayed.** Its `pct_complete` is physical progress of installed work against a
* planned quantity, banded `ahead`/`on_track`/`behind`. That is variance against a plan over time,
* not completeness of linkage between records — a different axis that happens to be spelled with a
* percent sign.
*/
type Ctor<T> = new (...args: any[]) => T;

export function withCoverageMaps<TBase extends Ctor<HttpCore>>(Base: TBase) {
return class CoverageMaps extends Base {
/** Discipline Spine traceability: discipline → sheets → specs → bid packages → cost codes → budget. */
spineTraceability(pid: string) {
return this.json<SpineTraceability>(`/projects/${pid}/spine/traceability`);
}

/**
* Scope register: every scope item checked for a quantity, an owner and a schedule slot, with
* the ones missing any of the three named individually in `gap_items`.
*/
scopeRegister(pid: string, body: {
scope_items: Record<string, unknown>[]; qto_lines?: Record<string, unknown>[]; activities?: Record<string, unknown>[];
}) {
type Item = {
id: string | null; name: string; cost_code: string | null; qty: number | null; value: number | null;
responsible: string | null; package: string | null; start: string | null; finish: string | null;
quantified: boolean; allocated: boolean; scheduled: boolean; gaps: string[]; status: "complete" | "gap";
};
return this.json<{
item_count: number; complete: number; with_gaps: number; pct_quantified: number; pct_allocated: number;
pct_scheduled: number; total_value: number; by_owner: { owner: string; value: number }[];
gap_items: Item[]; items: Item[]; note: string;
}>(`/projects/${pid}/scope/register`, { method: "POST", body: JSON.stringify(body) });
}
};
}
2 changes: 1 addition & 1 deletion services/api/test_file_sizes.py

Large diffs are not rendered by default.