Skip to content

Latest commit

 

History

History
276 lines (226 loc) · 62.8 KB

File metadata and controls

276 lines (226 loc) · 62.8 KB

CodeCleaners.PgProj — Delivery Progress Tracker

This is the master progress report. It tracks every delivery milestone toward SSDT-for-PostgreSQL parity and is the single place to read "where are we now". It is updated on every delivered milestone (see Update contract below).

Last updated: 2026-06-25 (M1–M7 complete in scope — every buildable epic shipped and is verified against live PG18; the three remaining M7 boxes are explicit non-goals, not pending work: EP-DESIGNER #112 is won't-do (read/round-trip from M5 #26 is the supported level), base types/transforms #102/#108 are won't-support (would require shipping C functions in the PG server — out of scope by decision), and EP-CICD #71 was removed earlier. The VS experience is VALIDATED IN THE INSTALLED PRODUCT: a 115-scenario VM E2E suite (FlaUI+DTE, real sample database) found and fixed three silent editor-chain breaks, live diagnostics reached build parity, navigation/completion are alias-aware and column-precise, and a file-level Sync-with-Database command shipped) · Streams: semantic core (EP-SEMCORE #41) · SSDT parity (#27) · performance (#12)

Legend: ✅ delivered · 🟡 in progress · ⬜ not started · ⛔ blocked · ⏸️ deferred.


1. Roadmap at a glance

Milestones map to the three tracking epics. GitHub milestones mirror this table (the issues are the deliverable subtasks; complex issues are broken into smaller subtasks when implemented).

# Milestone Stream Issues Done Status GitHub
M1 Determinism & Diagnostics Foundations cross-cutting #59, #49, #36, #37, #60, #62, #63 7 / 7 milestone/1
M2 Semantic Core — Identity & Foundations EP-SEMCORE #42, #43, #44, #45, #46, #39 6 / 6 milestone/2
M3 Semantic Core — Binding & Validation EP-SEMCORE #47, #48, #50, #51 4 / 4 milestone/3
M4 Diff, Risk, Deploy & Incremental EP-SEMCORE #52, #53, #54, #55, #56, #57, #58, #61, #64 9 / 9 milestone/4
M5 SSDT Parity — Editor UI parity #27 #31, #24, #25, #26 4 / 4 milestone/5
M6 Performance & Engine Backlog perf #12 #8, #10 2 / 2 milestone/6
M7 SSDT Parity — Engine, Tooling & Coverage parity #27 / coverage 7 in-scope epics (EP-CICD #71 removed; EP-DESIGNER #112 won't-do; #102/#108 won't-support); #66–#70,#72,#111 7 / 7 milestone/7

Already shipped (not milestoned — do not rebuild): the headless engine — build, compare, publish, validate, extract, drift, analyze, PgProj.Sdk, and 100% parser accept/reject parity vs PostgreSQL 18 on the 21,743-statement corpus. Parity-stream Phase-1 engine epics (.pgpkg, project variables, pre/post-deploy scripts, references, JSON-RPC) landed via waves 1–2; see docs/SSDT_PARITY_BACKLOG.md §2.


2. Milestones in detail

Each issue is a deliverable subtask. Sub-bullets are the smaller subtasks a complex issue is split into when work starts — fill them in (and check them off) as the issue is implemented.

M1 · Determinism & Diagnostics Foundations ✅

The reproducibility & quality base everything else leans on. Complete (7/7).

  • #59 — deterministic raw-object ordering across the parallel introspection merge (delivered 2026-06-07, commit 2200b6c; two introspections of the same DB now yield byte-identical models + deploy scripts)
  • #49 — unify diagnostics into one compiler-style type (file/line/col/code/severity/related) (delivered 2026-06-07, commit 949b766; unified Diagnostic in src/PgProj.Core/Diagnostics/, producers migrated, JSON contract kept stable, 8 unit tests. Follow-up #63: populate & surface Related[] end-to-end)
  • #36 — round-trip idempotency: phantom diffs + typed-table fidelity for raw object kinds (delivered 2026-06-07; identity-only compare for canonical-reconstruction kinds, typed-table OF type fidelity, aggregate identity double-schema bug fixed. Scoped to the declared kinds; the rest split to #61 (raw kinds) + #64 (finely-modelled), both M4)
  • #37 — integration tests on throwaway databases (isolation) (delivered 2026-06-07; ThrowawayDatabaseFixture per-class DB create/drop, 5 integration classes migrated off fragile shared-DB cleanup)
  • #60 — testing: golden-file scripts + CanonicalHash-stability + StableId-under-rename (golden-file determinism delivered in M1; CanonicalHash-stability + StableId-under-rename tests landed with #42's ObjectIdentityTests in the M2 merge — fully covered)
  • #62 — determinism: normalize source line endings so build artifacts are byte-reproducible (delivered in M2 merge; load-time LF normalization via SourceReader, CRLF/LF determinism test, golden stopgap simplified)
  • #63 — diagnostics: populate & surface related locations end-to-end (delivered in M2 merge; duplicate-def diagnostics carry a RelatedLocation, related on the DTO, ProjectBuildResult carries unified Diagnostic)

M2 · Semantic Core — Identity & Foundations ✅

EP-SEMCORE foundational concepts + Phases 1–2. Unblocks the rest of the semantic core. Complete (6/6).

  • #42 — Object Identity Model (ObjectId/StableId/CanonicalHash, Phase 9) — keystone for deterministic rename detection *(delivered in M2 merge; Model/Identity/ + Comparison/Canonicalizer
    • IdentityDiff classifier for #53; 20 tests. Follow-up: full Phase-8 canonical form refines it (#51))*
  • #43PostgresVersionProfile (capabilities / catalog-queries / object-capabilities) (delivered in M2 merge; all of LiveDatabaseReader's catalog SQL moved behind the profile, comparer asks ObjectCapabilities. Per-version query/ALTER overrides are the next increment)
  • #44IProjectObject extensibility contract + object-kind registry (delivered; one contract per kind + ProjectObjectRegistry, and every per-kind switch collapsed into the single ObjectKindRegistry tableRawObjectMeta, SchemaCompareObjectType.OfKind, the LiveDatabaseReader reader fan-out, and ModelBuilder.BuildRaw name-parsing all read it. Adding a kind that reuses existing styles = one registry row. Golden tests byte-identical; PG18 suite green. Follow-on: per-kind ownership of GenerateSql/Validate + CatalogBuilder symbol registration is a later deepening)
  • #45 — Phase 1: project model loading hardening (source positions persisted + real glob/exclude) (delivered in M2 merge; positions persisted at build → no re-parse, **/<Exclude> globbing)
  • #46 — Phase 2: global symbol table (identity entries, overload-keyed funcs, reverse lookup, search_path) (delivered in M2 merge; SymbolTable/SymbolEntry with StableId, signature overloads, reverse index)
  • #39 — simpler project format (delivered in M2 merge; SDK auto-includes **/*.sql, EnableDefaultSqlItems opt-out)

M3 · Semantic Core — Binding & Validation ✅

EP-SEMCORE Part A, Phases 3–8. Complete (4/4).

  • #47 — Phases 3–4: semantic binding → Bound AST → Typed Semantic Model (delivered; Semantics/Binding/ — refs resolve to SymbolEntry, exprs carry ResolvedType, view/CTE column inference, query API by symbol/location/reference for IDE features)
  • #48 — Phase 5: validation depth (type safety, overload resolution, view/trigger/constraint validity) (delivered; over the typed model with related locations; prove-before-erroring + managed-schema scoping → no false positives)
  • #50 — Phases 6–7: dependency graph (Hard/Soft/Runtime edges) + circular-dependency detection (delivered; Semantics/Dependencies/, Tarjan-SCC cycles naming the full path, reverse-closure API for Phase 15)
  • #51 — Phase 8: canonical model hardening (column-order, body canonicalization, type aliases) (delivered; broadened TypeNormalizer, canonical expression form, canonicalization in the model, gated column-order)

M4 · Diff, Risk, Deploy & Incremental ✅

EP-SEMCORE Parts B+C, Phases 10–15 & 18. Complete (9/9).

  • #52 — Phase 10: schema snapshots (delivered; schema.snapshot offline compare endpoint + staleness + pgproj snapshot CLI)
  • #53 — Phase 11: identity-based diff (delivered; gated rename detection + structured function/unique/sequence/enum deltas)
  • #54 — Phase 12: Deployment Risk Analyzer (delivered; Safe/Warning/Dangerous/DataLoss/Blocking + rationale + rewrite/lock flags)
  • #55 — Phase 13: deployment planning (delivered; topo-sort over the dependency graph, edge-class aware, skeleton-then-alter for hard cycles)
  • #56 — Phase 14: option- & version-aware script generation (delivered; idempotent IF EXISTS, timeouts, verbose, include/exclude, ObjectCapabilities ALTER paths; defaults byte-identical)
  • #57 — Phase 15: incremental analysis & object cache (delivered; CanonicalHash-keyed cache, reverse-closure invalidation)
  • #58 — Phase 18: options & profiles (delivered; comparison-equivalence options, ComparisonProfile, block-on-data-loss gate wired to risk)
  • #64 — round-trip idempotency (finely-modelled) (delivered with #53/#61)
  • #61 — round-trip idempotency (remaining raw kinds) (cast/operator/op-class/op-family + Trigger [event-order canonicalized] + function-comment [types-only signature] — all round-trip clean against PG18, in the guard)

M5 · SSDT Parity — Editor UI ✅

The "same experience" UI stream (#27). Complete (4/4).

  • #31 — EP-LSP: resident language service (pgproj serve / LSP) for live parsing (delivered; PgProj.Lsp + pgproj serve STDIO LSP host — didOpen/didChange→debounced publishDiagnostics [verdict identical to build], definition/hover/completion; pure handlers unit-tested [16 tests]; doc at docs/LSP_LANGUAGE_SERVER.md. This is the backend every editor client attaches to.)
  • #24 — EP-VSCODE: VS Code extension (primary UI) (editors/vscode/ — LSP client to pgproj serve, projects tree, commands, Publish + Schema-Compare webviews, diagnostics→Problems. 62 vitest tests, tsc/eslint clean, .vsix packages; E2E green [8 pass/1 pending] via a space-free-temp runner shim.)
  • #25 — EP-VS: Visual Studio experience (Route A: .pgproj builds/publishes via PgProj.Sdk, dotnet pack → consumable nupkg; Route B VSIX scaffolded in its own solution [needs the VS SDK to build]; LSP client for VS. docs/VISUAL_STUDIO.md.)
  • #26 — EP-DESIGNER: graphical table designer (designer webview + engine describe-table/emit-table verbs — .sql generation stays in SqlEmitter; byte-stable round-trip proven over the corpus.)

Editor UIs are validated to the extent runnable here (unit + tsc + .vsix package + the .NET engine/round-trip layer

  • the VS Code E2E); a full VSIX build + VS Apex UI tests need the Visual Studio SDK host.

M6 · Performance & Engine Backlog ✅

Open items from the perf/deploy-sync tracker (#12). Benchmark-gated (bytes/op); corpus byte-identical. Complete (2/2).

  • #8 — reduce ModelBuilder allocation (delivered; lazy table-constraint lists — All 16.16→16.06 MB/op, Table bucket 1.77→1.66; dashboard stage #24. Safe-by-construction, no footgun.)
  • #10 — profile the comparer / diff path (CompareBenchmarks) (delivered; representative CompareBenchmarks + comparer fast-paths — −19% to −40% allocated/op across the compare path; behavior-preserving.)

M7 · SSDT Parity — Engine, Tooling & Coverage ✅ (complete in scope)

Scope-complete (7/7 in-scope epics). Every buildable epic shipped and is verified against live PG18. The three boxes that remain are explicit non-goals decided by the owner (2026-06-25), not pending work — see Out of scope below.

Opened as a 55-issue backlog from docs/SSDT_PARITY_BACKLOG.md §3 + the open Introspect rows in COVERAGE.md, then audited against the code: both source docs were stale — 4 of the 9 epics had already been delivered during the M-waves (the boxes were never checked). Those 4 epics + the already-done child tasks of the partial epics were closed with evidence comments (27 issues). Then the remaining engine work was built & verified vs PG18, EP-CICD was removed, and base types/transforms were backlogged → 6 of 8 in-scope epics done (EP-CICD #71 removed). Each epic is a tracking issue; tasks are linked children.

Delivered (already on main; closed after audit):

  • #66EP-TARGET: target-platform enforcement — TargetVersionAnalyzer + PgVersionCapabilities/SupportedFeatures table + PGV###; gate wired into build/publish/validate. Tests TargetVersionTests/VersionProfileTests.
  • #68EP-PROFILE: publish profiles — Deployment/PublishProfile.cs (secret-whitelisted) + profile create + --profile (CLI>profile>default). Tests PublishProfileTests.
  • #69EP-SCHEMACOMPARE: unified two-way Comparison/SchemaCompare.cs + selectable SchemaChangeSet + --output diff.json + --exclude. Tests SchemaCompareTests.
  • #70EP-TEMPLATES: Templates/* + add/new project verbs + dotnet new pack at templates/. Tests TemplateTests/TemplateIntegrationTests.
  • #67EP-ANALYSIS+: config (#77), --rule (#78), SARIF (#80), external rule packs (#79IPgRule + RulePackLoader), and rules PG006/PG008. Doc docs/ANALYSIS_RULES.md. Epic closed; #81 (grow the rule set) stays open as ongoing backlog.

Delivered:

  • #72EP-COVERAGE: live-server introspection. Done: matview (#100), COMMENT ON (#105), aggregate (#106), cast/operator/op-class/family (#107), EXCLUDE (#98), EVENT TRIGGER tags (#104), POLICY TO roles (#103), USER MAPPING + LANGUAGE (#108), PARTITION/INHERITS (#99), index opclass/ordering (#101), expression statistics (#110), TEXT SEARCH PARSER/TEMPLATE (#109) — plus (already in code) collation/conversion/FDW/server/foreign-table/TS-config+dict/range/column-statistics/publications. All shipped work verified against PG18. (Base types #102 + transforms #108-tail are out of scope — see below.)
  • #111EP-VS: Visual Studio Route B VSIX + slngen grouping — all six children #113–#118 delivered 2026-06-11: real CPS .pgproj project type + templates + VS-loadable SDK (#113), .vsct/manifest wiring (#117), four property pages as PgProj.Sdk CPS rules incl. PgProjPublishVariables--var (#114), modal Publish dialog (#115), interactive Schema Compare window with pickers/checkable diff/Generate Script/Apply (#116), and pgproj sln new|add|list solution grouping (#118, PgProj.Core.Solutions, 15 tests). Both VSIXes build headless-green (editors/vs/build-vsix.cmd); the runtime F5 pass was later validated in the installed product (115-scenario VM E2E, see the delivery log).

Out of scope (owner decision 2026-06-25 — not pending work, will not be built):

  • #112EP-DESIGNER (editable designer): won't do. The read/round-trip designer from M5 #26 (describe-table/emit-table, byte-stable over the corpus) is the supported level; the user hand-writes SQL (the graphical designer is parked, not pending). Children #119/#120 closed not-planned.
  • #102 base types + #108-tail transforms: won't support. These require shipping C functions inside the PostgreSQL server, which is outside what this tooling targets ("nobody can have it all"). Closed not-planned.
  • #71EP-CICD: removed from M7 scope (user decision). #97 (stable exit-code contract) was delivered (ExitCode.cs + ExitCodeContractTests + docs/CICD.md) → closed done; the GitHub Action / Azure DevOps task / container-image tickets (#94/#95/#96) closed not-planned; the opt-in ci/azure-devops/ template removed. The CLI stays CI-friendly via the documented exit codes + --fail-on-changes/--dry-run.

M7 is complete in scope. The only perpetually-open item is #81 (grow the analyzer rule set), which is an ongoing backlog by design, not a milestone gap.

Next wave — prepared & ready to implement (opened 2026-06-24)

Scoped, unblocked tickets forming the next implementation push (designer #112/#119/#120 stays parked; #102/#108 stay blocked pending C functions in the server). All on milestone M7:

  • #133 EP-REF — NuGet .pgpkg package references — delivered 2026-06-24. All three children done:
    • #147dotnet pack a project's built .pgpkg into a consumable .nupkg (pgpkg/<Name>.pgpkg, id/version) — 7344705.
    • #148ReferenceResolver resolves a PackageReference from the restored NuGet global packages folder, reference-only (no deploy DDL); unresolvable → PGREF006 — b110bc3.
    • #149 — consumer/inlined deploy parity proven on the live shadow DB — c445f8d.
  • #150 (child of #139 EP-UNITTEST) — pgproj test stub scaffolder (pre/test/post from the semantic model, leading-_ naming) — delivered 2026-06-24, b83e94b. Completes EP-UNITTEST #139.
  • #151 (child of #134 EP-EXTRACT) — .pgpkg-embedded data/ COPY section (BACPAC-analogue variant) — delivered 2026-06-24, 0a06a3d. Completes EP-EXTRACT #134. Solved the COPY-vs-GENERATED ALWAYS AS IDENTITY blocker by relaxing the column to BY DEFAULT around the load; verified end-to-end against live PG18.
  • #152 (child of #136 EP-REFACTOR) — expand-wildcards command (SELECT * → explicit columns, model-resolved) — delivered 2026-06-24, 7809b6d. Completes EP-REFACTOR #136.

3. Delivery log

Newest first. One line per delivered issue/milestone; this is the audit trail of what shipped when.

Date Milestone Item Commit Notes
2026-07-03 M7+ EP-TESTGEN #157: installed VS extension now carries the xUnit generator + HyperV VS E2E + Generate Tests auto-adds the project to the solution. The Hyper-V VM's installed classic extension predated the .test.sql→xUnit pivot, so its right-click "Generate Tests" still emitted .test.sql (useless in a C# solution). Rebuilt + reinstalled the classic VSIX (its bundled PgProj.Cli.dll is now today's build), so the command emits a standalone C# xUnit project. New blackbox scenario ToolingOperationScenarios.Generate_tests_emits_a_csharp_xunit_project_not_the_retired_sql runs the INSTALLED extension's bundled CLI test generate on the loaded .pgproj and asserts the C# shape (.csproj+xunit, PgDatabaseFixture, schema.sql, Generated/*.g.cs) with zero .test.sql — locking in both the pivot and the stale-bundled-CLI class (VsFixture.RunBundledCli helper; no DB needed — generate reads the model). vm-testgen-e2e.ps1 gained the same no-.test.sql guard. UX bump reported by owner: the generated project wasn't added to the open solution — GenerateTestsDialog now calls Solution.AddFromFile on the emitted csproj (idempotent; a hiccup degrades to a guidance line, never a thrown error) so it lands in Solution Explorer / Test Explorer automatically. 6fe141c (main) Verified in the Hyper-V VM against the freshly reinstalled extension: ToolingOperationScenarios 5/5 green; CLI-path VM E2E 32 passed / 17 skipped / 0 fail against the live sample DB (guard included). Classic VSIX rebuilt clean; UiTests compile 0-warning. Known follow-up (owner noted "some bumps"): the dialog's default output <proj>\Tests\… sits inside the .pgproj glob root, so its schema.sql can be picked up by a .pgproj build — candidate to relocate the default to a sibling folder.
2026-07-02 M7+ Parser P2 batch (audit tail): function return types + bit/hex literal validation + dollar-tag rule. (1) Binder.FunctionReturnType was hardcoded Unknown behind a stale "symbol table does not yet store return types" comment — CatalogBuilder has stored the normalized RETURNS type since #48; the hook now consumes it (SETOF/TABLE/trigger/record conservatively stay Unknown), so function-routed expressions finally carry a real type. (2) B'…'/X'…' content validated at the lexer with the same lone-prefix rule as E-strings — PG rejects B'2'/X'GG' at parse time (live-verified); U&'…' deliberately stays DB-tier (a trailing UESCAPE clause changes the escape char — undecidable at the lexer without false positives). (3) Dollar-quote tags follow identifier rules: $1$foo$1$ is positional param $1 + an unterminated $foo$ string, exactly PG's verdict (live-verified). 4a02ac1 (branch fix/parser-p2, merged to main) 4 new BitHexLiteralTests; full Core suite 25,260/0 with live PG18, 0 skipped. Remaining audit backlog (all M/L): BETWEEN/IN precedence bands, JSON/XML irregular-call args, partitioned/foreign-table structured model, SET search_path, perf nits, DbFact→Fact promotion sweep — tracked in the parser-weak-points-audit memory.
2026-07-02 M7+ Parser P1 batch — the audit's remaining expression/lexer/compare findings, every fix ground-truthed against live PG18. (1) OperatorLexer applies PG's trailing-sign rule (a multi-char operator only ends in +/- when it contains one of ~!@#%^&|?), so a<=-1 lexes as a <= -1 instead of the bogus <=- operator that silently lost the unary minus; live-verified that 2^-2 correctly STAYS the single ^- operator (PG itself errors there). (2) ^ made LEFT-associative (2^3^2 = 64, live) — and the audit's -2^2 finding was debunked by the oracle (PG returns 4 = (-2)^2; the parser was already right; now pinned by test). (3) Real numeric-literal grammar (one decimal point, the dot-dot rule, exponent requires digits) + PG's "trailing junk after numeric literal" as a hard LexError — 1.2.3/1e+/1a/1e5e6/0x1G now match the server verdict, closing the literal slice of the DbFact false-green meta-issue. (4) ROLLUP/CUBE/GROUPING SETS argument lists fold into SelectQuery.GroupBy (nested lists, empty sets). (5) Integer literals typed by PG's smallest-fit rule (integer/bigint/numeric) + an unambiguous numeric-widening pass in overload resolution — kills the false "no function matches" on fn(42) vs fn(integer) among overloads. (6) Canonicalizer.NormalizeText preserves string-literal CASE — a case-only literal edit in a default/CHECK/view/function body now deploys instead of comparing equal (silent no-deploy). Bonus: deflaked PackageVerifierTests.Extract_round_trip (it introspected the SHARED admin DB twice non-atomically while parallel live tests mutated the server — reproduced twice today; now uses a private throwaway DB like every other live test). 0b413ef, c1cbc39, 44f17b2, 8820199, 81d0c88 (branch fix/parser-p1, merged to main) 21 new tests across 5 files, assertions annotated with their live-PG18 results; full Core suite 25,256/0 with live PG18, 0 skipped. With this batch EVERY P0 and P1 from the 2026-07-02 fleet audit is resolved (one debunked with oracle evidence); remaining P2/P3 backlog stays in the audit memory.
2026-07-02 M7+ Semantics P1 pair: ALTER-aware catalog + narrowed validation blankets. (a) One AlterStatement anywhere in a file used to disable View/Trigger/CHECK/query validation for the WHOLE file — including the extractor's routine standalone ADD CONSTRAINT, so extracted projects got far weaker validation than hand-written ones (SemanticAnalyzer had the same any-ALTER blanket on column checks); (b) CatalogBuilder ignored ALTERs entirely, so a view over an ALTER-added column false-positived "column does not exist" — the very reason the blankets existed. Fix: Catalog.AmendRelation folds standalone ALTER TABLE column changes (add/drop/retype) into the semantic catalog mirroring ModelBuilder's fold (best-effort; a dropped column keeps its append-only symbol → missed diagnostic, never a false positive); both analyzers narrow to AlterStatement.InvalidatesBinding — only RENAME / SET SCHEMA / OF / INHERIT / PARTITION-class actions still force the conservative skip. c4c1a84 (branch fix/parser-p0) 5 new AlterAwareValidationTests. Full Core suite 25,235/0 with live PG18 — the corpus's ALTER verdicts confirm no new false positives. CLI proof: pgproj build on CREATE + ADD CONSTRAINT + bad view now exits 5 with column "missing_col" does not exist (previously built green).
2026-07-02 M7+ Parser/deploy P0 batch 4 — constraint attributes survive to the model/deploy/compare; ALL SIX audit P0s now resolved. DEFERRABLE / INITIALLY DEFERRED, NOT VALID, INCLUDE, NULLS NOT DISTINCT, NO INHERIT and MATCH FULL were parsed + cross-validated then DISCARDED before the model — the committed golden deploy script itself proves AllFeaturesDb's declared INCLUDE ("email") / UNIQUE NULLS NOT DISTINCT / DEFERRABLE INITIALLY DEFERRED FK were silently stripped from deploys. Full sweep: model records (appended defaulted params), ModelBuilder folds (both table-level and inline nodes), SqlEmitter renders only when non-default (attribute-free constraints emit byte-identically; NOT VALID stays a change-level ALTER suffix), comparer PK-equality + unique/FK signatures (bonus: FK ON DELETE/ON UPDATE now in the signature — referential-action flips were invisible to compare; NO ACTION ≡ absent; NOT VALID = validation-state, excluded from matching so validated-live never churns), introspection (pg_constraint attrs + backing-index INCLUDE columns + indnullsnotdistinct behind the first per-version CatalogQueries fork — PG13/14 read constant FALSE; check reader strips the NOT VALID/NO INHERIT def-text suffixes that previously leaked into the expression), identity canonical-form markers non-default-only (historical hashes preserved). Bonus fix surfaced by the live round-trip: a table-level PRIMARY KEY now marks its columns NOT NULL in the model (inline always did) — kills a phantom alter-column diff vs every live DB. Designer DTOs stay attribute-lossy (parked feature; CollectCompanions re-emits from the model). 6e3d338 (branch fix/parser-p0) 8 new ConstraintAttributesTests; both goldens regenerated (diff = the attributes now actually deploying + NOT NULL on the identity PK column). Full Core suite 25,230/0 with live PG18, 0 skipped. Live proofs: publish lands every attribute in pg_constraint/pg_index; extract emits them verbatim; project-vs-live AND extract-vs-live both converge to "No differences". PG also vetoed an FK onto a DEFERRABLE PK (55000) mid-test — an error the old silent strip would have masked.
2026-07-02 M7+ Parser/deploy P0 batch 3: DeploymentPlanner wired into publish/script — issue #160 closed properly. The planner (#55) + dependency graph (#50) shipped with zero production callers; every deploy path used bare OrderBy(Phase), so a function-dependent view scripted before its function (42883 on greenfield). Now: ComparerOptions.DependencyGraphSchemaComparer.Compare returns DeploymentPlanner.Plan(...).AllSteps (phase order refined by real Hard/Soft edges; skeleton pass on hard cycles; byte-identical to the phase sort when no edge binds — asserted); new DeploymentGraphFactory.TryBuild(project) (parse → one Catalog, two passes → ReferenceCollector → graph; best-effort, null degrades to phase order); DeployOptions.PreserveChangeOrder stops the generator's internal phase re-sort from undoing the refinement. Wired at PublishService.PlanAsync (CLI publish/deploy-report + VS) and the CLI script verb; .pgpkg sources keep phase order. Limitation: --parallel (PhasedDeployer) is phase-barrier-parallel by design — refinement doesn't apply there. NOT done by reordering phase numbers (lab-note rule; goldens stay pinned). c30d305 (branch fix/parser-p0) 4 new DependencyOrderedCompareTests (incl. the #160 baseline + no-edges equivalence). Full Core suite 25,222/0 with live PG18. Live proof: the exact #160 repro (view calling a function) now publishes greenfield and pgproj script emits the function first. One non-reproducible PackageVerifierTests.Extract_round_trip flake in one full run (green in isolation and on full rerun) — watch for a live-DB parallelism race.
2026-07-02 M7+ Parser/deploy P0 batch 2: standalone ALTER TABLE ADD/DROP/ALTER COLUMN now folds into the table model — previously only ADD CONSTRAINT folded (#153); every other valid ALTER TABLE action was parsed, validated, then silently discarded (column invisible to comparer/deploy/testgen, zero diagnostics). Parser captures details structurally (AddedColumns = the same ColumnDef node CREATE TABLE uses, DroppedColumns, ColumnActions for TYPE / SET|DROP DEFAULT / SET|DROP NOT NULL); ModelBuilder folds via shared FoldColumnDef; DROP COLUMN also removes PK/UNIQUE/FK involving the column (PG auto-drop semantics; CHECK stays best-effort). AlterStatement.FoldsIntoTableModel is the single fold predicate, shared with TableDesigner's companion skip — double-fold-safe by construction. Non-structural actions (OWNER/SET/RLS/RENAME) still pass through; surfacing those is the follow-up analyzer rule, and CatalogBuilder folding is deliberately deferred to the SemanticValidator-blanket-disable P1 (no observable effect until then). 29df7a6 (branch fix/parser-p0) 5 new AlterTableFoldTests; full Core suite 25,218/0 with live PG18, 0 skipped (no designer/corpus double-fold storm — the #153 Single→2 signature stayed quiet). Live proof: a project whose column exists only via ALTER TABLE ADD COLUMN + ALTER COLUMN TYPE deploys it greenfield and compare converges to "No differences".
2026-07-02 M7+ Parser/deploy P0 batch 1 (from the 10-agent fleet audit, findings in the parser-weak-points-audit memory; branch fix/parser-p0): (1) unterminated block comment / string / quoted ident / dollar-quote now surface as hard diagnostics — previously they consumed silently to EOF with FullyRecognized=true, so an unclosed /* before a CREATE TABLE made that table VANISH from the model (and deploys) with zero warnings; the tokenizer records a LexError (first only) and PgParser converts it to a ParseDiagnostic with real line:column. (2) GENERATED ALWAYS AS (expr) VIRTUAL (and the PG18 storage-omitted form) no longer collapses into STOREDGeneratedStored.IsStored on the AST, ColumnDefinition.GeneratedIsStored in the model, emitter renders STORED|VIRTUAL, comparer flags kind flips, introspection maps attgenerated='v' (it previously mis-read virtual columns as plain columns with a bogus DEFAULT), identity hashes virtual as genv: (stored keeps gen: — existing snapshots unaffected). (3) A changed MATERIALIZED-view body now deploys as drop+recreate via new RecreateMaterializedViewChange (non-destructive; RiskAnalyzer Warning) — the old CREATE … IF NOT EXISTS path was a silent no-op: publish said "1 change successfully" while the live body stayed stale (reproduced live pre-fix). a9d9647, 7f008d9 (branch fix/parser-p0) 15 new tests (TokenizerLexErrorTests 8, GeneratedVirtualColumnTests 4, MaterializedViewRecreateTests 3); model-JSON golden regenerated (new field only). Full Core suite 25,213 / 0 with live PG18, 0 skipped. Live proofs: PG18 DB with STORED+VIRTUAL columns extracts correctly and compares to "No differences"; matview body publish A/B — stale CLI silently no-ops, fixed CLI applies (pg_matviews) and converges. Remaining P0s tracked in the audit memory: ALTER-form model folds, DEFERRABLE/NOT VALID/INCLUDE capture, DeploymentPlanner wiring (#160-proper).
2026-07-02 M7+ EP-TESTGEN: explicit DB-mode choice + seed-hook surface v2 — the generated suite's database source is now a first-class, user-chosen setting instead of a runtime-only env-var autodetect. XunitSuiteSettings gains DbMode (Auto/Testcontainers/ExistingConnection), GenerateSeedHooks, TestConnection; CLI: pgproj test generate … [--db-mode auto|container|existing] [--connection <admin-conn>] [--no-seeds]existing mode drops the Testcontainers.PostgreSql package from the emitted csproj entirely and requires PGPROJ_TEST_CONNECTION with a clear startup error; --connection writes a git-ignored <Name>.local.runsettings auto-applied via a conditional RunSettingsFilePath in the csproj (dotnet test + VS Test Explorer pick it up with zero flags; secrets never land in committed files, matching the ConnectionStore convention). Fixture hardened: Docker-start failure now throws an actionable message naming the escape hatch; a failed InitializeAsync calls DisposeAsync() itself so a failed deploy never leaks a pgproj_test_* DB or container (xUnit v2 skips DisposeAsync after init failure); deploy-failure message includes rendered-profile vs live server version. Seed surface v2: suite-level partial void SeedSuite(conn, tx) hook (scaffold-once Seeds/SuiteSeed.cs, runs ONCE after deploy, COMMITTED) + Generated/BaselineRows.g.cs with idempotent per-table Insert_<schema>_<table> helpers; ALL generated setup INSERTs now use ON CONFLICT DO NOTHING (asserted statements stay strict) so committed seeds compose with generated tests instead of tripping 23505. Also emits README.md + .gitignore. VS right-click rebuilt: GenerateTestsDialog : DialogWindow (mirrors ImportDatabaseDialog) — auto/Docker/existing radios, connection box prefilled from ConnectionStore (new purpose key "testgen", so it never clobbers the import connection), category checkboxes, output-folder box (-o finally wired), seed-hook + force toggles; the CLI now runs async off the UI thread (the old dialog blocked VS up to 600 s via JoinableTaskFactory.Run). Gotcha fixed en route: an XML comment containing --connection made vstest reject the runsettings ("An XML comment cannot contain '--'"). feature/testgen-suite-generator (pending) XunitSuiteScaffolderTests grown 9→19 (mode-pinned fixtures, csproj package gating, runsettings emission+escaping+no---, eager cleanup, SuiteSeed wiring, --no-seeds, BaselineRows, README). Full Core suite 22,640 / 0. All 3 modes generate + compile 0-warning; live proofs: existing-mode ran green via runsettings only (no env var in shell), Testcontainers mode green (real Docker), regen over a suite with an implemented SuiteSeed preserved it and stayed green (composability proven — this exact scenario failed 4 tests before the ON CONFLICT change). Hyper-V E2E green: 32 passed / 17 skipped / 0 fail (VM resolved via test-makina.mshome.net; Default-Switch subnet drifted again — see lab note). Classic VSIX rebuilt clean with the new dialog + bundled CLI.
2026-07-01 M7+ EP-TESTGEN reshaped: pgproj test generate now emits a STANDALONE xUnit test PROJECT (supersedes the .test.sql path per owner decision) — the generator no longer writes SQL run by a bespoke runner; it emits a normal C# xUnit project so dotnet test (and the VS Test Explorer) is the only tool anyone needs — CI/CD-runnable, no PgProj tooling. New XunitSuiteScaffolder walks the built model and emits: a .csproj (xunit + xunit.runner.visualstudio + Testcontainers.PostgreSql + Npgsql), a collection PgDatabaseFixture (spins its OWN PostgreSQL via Testcontainers/Docker, deploys the greenfield schema once, then every test runs in a rolled-back transaction → full isolation; or, when PGPROJ_TEST_CONNECTION is set, uses that existing server via a created-then-dropped throwaway DB — no Docker, so it runs on a Docker-less agent/VM and in CI with a Postgres service container), a PgTestBase (Npgsql helpers: AssertSqlState/AssertNotEmpty/AssertTrue), and per-table partial test classes reusing BaselineRowSynthesizer for the INSERT/prelude SQL — negatives assert PostgresException.SqlState (23502/23505/23503), CRUD asserts the row landed, views SELECT … LIMIT 0, functions/procedures/triggers/CHECK become [Fact(Skip=…)] (visible-but-inconclusive). The user's data-injection hook is a regen-safe partial void Seed(NpgsqlConnection, NpgsqlTransaction) per table in Seeds/*.Seed.cs (NEVER overwritten; runs before each generated INSERT). schema.sql is emitted statement-delimited and the fixture deploys it order-tolerant (defers a statement that fails on a not-yet-created dependency and retries to a fixpoint) — this sidesteps the pre-existing deploy-ordering bug (#160: function-dependent view before the function, 42883) at the harness level WITHOUT touching the shared comparer's phase order (which would break committed golden-script tests). Removed the SQL path entirely: SuiteScaffolder, TestScaffolder, PgUnitRunner, pgproj test <proj> runner + test scaffold (and their tests). CLI is now pgproj test generate <proj> [-o <dir>] [--categories …] [--namespace] [--name] [--image] [--force]no connection at generation time. Classic VS "Generate Tests (PgProj)…" dialog simplified (categories only; no connection/mode/run). feature/testgen-suite-generator (pending) New XunitSuiteScaffolderTests (9 offline: scaffold-once vs regen files, container+order-tolerant fixture, env-var existing-connection fallback, regen-safe Seed hook, 23502/23505/23503 asserts, depth-1 FK-parent seed, delimited schema.sql, category filter, view LIMIT 0). Real end-to-end proof (host): generated from the extracted sample-db, dotnet test spun its own PG18 container and ran green — 32 passed / 17 skipped / 0 failed (skips = CHECK/routine stubs + a depth-2 FK chain awaiting a Seed hook); the env-var path ran identically green in 122ms with no container. Hyper-V E2E (committed tests/testgen-vm-e2e/, headless over ssh): host publishes the CLI → ships to the VM → in-VM extract → test generate → assert project shape → dotnet test against the host Postgres (env-var throwaway DB, no Docker in the VM) → 32 passed / 17 skipped / 0 fail. Full Core suite 22,629 / 0 (2,558 live skipped, no conn); solution + classic VSIX build clean.
2026-06-25 M7+ EP-TESTGEN: complete unit + integration test-suite generator (#153; children #154–#158) — extends EP-UNITTEST from one-stub-at-a-time to a whole-project, auto-asserted suite. New SuiteScaffolder walks the built model and emits one .test.sql per (object, category): NOT NULL/UNIQUE/PK negatives (@expect-sqlstate 23502/23505), FK orphan (23503), CRUD round-trip, view/matview queryability, fn/proc/trigger stubs (via TestScaffolder), and catalog existence smoke tests for the other ObjectKinds. Internal BaselineRowSynthesizer builds a minimal valid INSERT (deterministic literal-per-type, skips identity/serial/generated/default/nullable, seeds depth-1 FK parents with OVERRIDING SYSTEM VALUE), DOWNGRADING to an inconclusive stub rather than emit a false assertion (enum/UDT column, non-trivial CHECK, FK outside the model). New CLI pgproj test generate <proj> [--connection] [--mode preserved|wipeout] [--categories] [-o] [--run]: preserved brings the DB up to date via PublishService (data kept), wipeout DROP+recreates (gated by --allow-wipeout), then writes Tests/Generated/ replacing only its own sentinel-bearing files, and optionally runs the suite. Classic VS extension gains a "Generate Tests (PgProj)…" project-context command (shells the bundled CLI). Verifying the COMPLETE feature against the rich EXTRACTED sample-db (not a toy inline schema) caught 3 defects, all fixed: (a) fn/proc/trigger unit stubs FAILED when run unedited → now run pgproj_inconclusive first; (b) Comment objects collided on one file name (silent overwrite) → skipped + dedup guard; (c) standalone ALTER TABLE ADD CONSTRAINT (the form pgproj extract emits) was invisible to the whole model — parser kept only the verb, ModelBuilder ignored it, so comparer/deploy AND the generator missed those FKs. Fixed in the model core: parser captures the constraint into AlterStatement.AddedConstraints, ModelBuilder folds FK/PK/UNIQUE/CHECK via shared ApplyTableConstraint; reconciled the redundant TableDesigner fold (the double-fold caused 183 Single→2 failures). PR #159. ea80998, 1396dcf, 9a91421 (PR #159, branch feature/testgen-suite-generator) SuiteScaffolderTests (15 offline) + ConstraintAndColumnTests ALTER-fold cases + live round-trip SuiteScaffolderIntegrationTests (PG18). Full Core suite 25,208 / 0 with live PG18; full generated suite against the rich extracted sample-db 0-fail on a clean deploy (FK tests generate, CRUD/PK seed parents, only genuine stubs INCONCLUSIVE); CLI e2e wipeout --run 12 pass/1 inc/0 fail (idempotent regen + no-clobber proven); HyperV VS UI E2E 4/4 in the installed VS 2026. Known limits documented: CRUD/insert tests target a clean schema; cross-file ALTER is best-effort; a separate pre-existing deploy-ordering bug (function-dependent view before the function, 42883) → issue #160.
2026-06-25 M7 M7 closed — complete in scope (7/7 in-scope epics). Owner confirmed the three remaining boxes are non-goals, not pending work: EP-DESIGNER #112 won't-do (M5 #26 read/round-trip is the supported level; user hand-writes SQL), base types #102 + transforms #108-tail won't-support (would require C functions in the PG server — out of scope), EP-CICD #71 already removed. Tracker reframed: §1 M7 row → ✅, §2 recharacterized as Out of scope. (docs only) No code change. #81 (analyzer-rule backlog) stays open as ongoing-by-design. All in-scope M7 work verified against live PG18 in prior rows.
2026-06-24 M7+ EP-REF: NuGet .pgpkg package references done (#133, closes #147/#148/#149) — dotnet pack packs a project's built .pgpkg into a .nupkg under pgpkg/<Name>.pgpkg (id=name, version=<Version>); ReferenceResolver resolves a <PackageReference> from the restored NuGet global packages folder (NuGetPackageLocator) and loads it reference-only — objects widen validation/binding but never enter the comparer's model, so no deploy DDL; unresolvable → PGREF006. CLI/SDK: pack via dotnet pack, consume via <PackageReference> after dotnet restore 7344705, b110bc3, c445f8d dotnet pack smoke (nupkg carries pgpkg/AllFeaturesDb.pgpkg, id/version correct); reference resolution tests (resolve from temp feed, reference-only never-emitted, unresolvable PGREF006); live shadow-DB parity (consumer-with-package deploys schema-identical to the inlined baseline; cross-schema view queryable). Reference/packaging suites 40/0 with PG18.
2026-06-24 M7+ EP-EXTRACT: .pgpkg-embedded data COPY section done (#151, closes #134) — the schema+data BACPAC analogue: PgPkg gains a data/ section (index + FK-ordered COPY payload per table, outside the source checksum); DataExporter.ExportCopyAsync (COPY (SELECT … ORDER BY key) TO STDOUT) + CopyDataLoader (COPY … FROM STDIN, then setval). Solves the COPY-vs-GENERATED ALWAYS AS IDENTITY blocker by relaxing the column to BY DEFAULT around the load and restoring ALWAYS. CLI: `extract --package <out.pgpkg> [--all-table-data --table-data …]produces it;publish ` loads the data after schema 0a06a3d
2026-06-24 M7+ EP-REFACTOR: expand-wildcards done (#152, closes #136) — pgproj expand-wildcards <project> <schema.view> resolves a view's SELECT * / alias.* to an explicit, model-derived column list and rewrites the .sql in place + records it in .pgrefactorlog. WildcardExpander does minimal comment/string/dollar-quote/paren-aware surgery — only top-level star tokens change, the rest stays byte-identical (so count(*) and '*' literals are never touched); bare * over multiple sources is alias-qualified 7809b6d (branch feature/m7-tail-children) New ExpandWildcardsTests (single-table, joined/aliased t.*, two-source bare *, count/literal safety, no-star + missing-view errors). Solution build green; 35 refactor/test-suite tests pass.
2026-06-24 M7+ EP-UNITTEST: test stub scaffolder done (#150, closes #139) — pgproj test scaffold <project> <schema.object> builds the model and emits a pre/test/post unit-test stub for a function/procedure/trigger to Tests/_schema.object.test.sql; leading _ keeps it out of the build glob while .test.sql keeps it discoverable by pgproj test. Functions get a scalar-assert body, procedures a CALL, triggers a DML-fires note; arg types render as NULL::type b83e94b (branch feature/m7-tail-children) New TestScaffolderTests (6: function/procedure/trigger shapes, no-arg, unknown/unqualified errors) + CLI smoke (scaffold → build still excludes the stub).
2026-06-24 M7+ Blackbox test suite — new tests/PgProj.Blackbox.Tests drives the pgproj CLI end-to-end against a live dockerized source+target (tests/blackbox-db): basics, build/publish happy + failure-recovery, data-compare, extract+sync, refactor, snapshot/package, test-runner; plus a DB-backed VS Code E2E (db.test.ts, runTest.ts forwards the Docker connection strings + built CLI dll) and VS UiTests tooling scenarios. Wired into PgProj.slnx. 5fa4f87 Closed the four fully-delivered epics this validates: #132 EP-DATACOMPARE (f2d6330), #137 EP-DEPLOY (5fbd9f1), #140 EP-PROFILE (5fbd9f1), #142 EP-PKG (0f81c34). Commented blackbox coverage on the still-open tails #139/#134/#136.
2026-06-17 M7 EP-ANALYSIS+: six new analyzer rules (#81) — PG017 json column (prefer jsonb), PG019 FK with no ON DELETE/ON UPDATE action, PG020 EXCEPTION WHEN OTHERS, PG021 SELECT … INTO without STRICT (per-file); PG024 duplicate index + PG025 redundant (leading-prefix) index (model-level, b-tree-ordered, explicit-only, partial-aware) 278518b New PgAnalyzerTests (PG017/PG020/PG021) + ModelAnalyzerTests (PG019/PG024/PG025 incl. column-order/predicate/partial negatives); registry-consistency extended; documented in docs/ANALYSIS_RULES.md. Full DB-free suite 22,615 pass / 0 fail. #81 stays open as the ongoing rule backlog.
2026-06-13 M7+ EP-UNITTEST: PL/pgSQL unit-test runner done (#139, core) — pgproj test <project> --connection [--deploy] discovers *.test.sql, runs each inside its own BEGIN … ROLLBACK (single-transaction scope, residue-free), reporting passed/failed/inconclusive with a non-zero exit (new code 10 TestFailed). PgUnitRunner ships an assertion prelude (the predefined conditions: assert, row count, scalar, empty/not-empty, column-type/expected-schema, data checksum, plus pgproj_inconclusive) and an -- @expect-sqlstate: directive for negative/expected-exception tests; --deploy applies the project schema to a throwaway shadow DB first 97db767 New PgUnitRunnerTests (conditions pass/fail, expected-SQLSTATE incl. wrong/none, inconclusive, single-transaction-scope residue check, directive parser) + a CLI test --deploy smoke. Full suite 25,164 / 0. Open in #139: the stub scaffolder.
2026-06-13 M7+ EP-EXTRACT: table-data extract done (#134, core) — pgproj extract --all-table-data / --table-data schema.table adds an FK-ordered Scripts/PostDeploy.sql data seed to the extracted project (excluded from Build, wired as the PostDeploy script, so a normal publish loads schema then data); DataExporter emits INSERT batches parents-before-children, writes identity columns with OVERRIDING SYSTEM VALUE, and setval-corrects each identity/serial sequence past the loaded rows 8a9fe6e New DataExporterTests — a live extract→load round-trip proving rows + identity + next-id sequence reproduce, and --table-data selection — plus a CLI smoke (extracted project builds). Full suite 25,156 / 0. Open in #134: the .pgpkg-embedded data/ (COPY) variant.
2026-06-13 M7+ EP-DATACOMPARE: row-level data compare + sync done (#132) — pgproj data-compare --source --target [--tables] [-o diff.json|sync.sql] [--apply]: DataCompare reads both live models, picks each table's key (PK, else a UNIQUE constraint), streams rows and buckets them Different / Only-in-Source / Only-in-Target / Identical with per-column diffs; keyless / one-sided tables are reported as skipped. GenerateSyncScript emits a deterministic DELETE→INSERT→UPDATE script (children-first deletes, parents-first writes) that --apply runs on the target in one transaction; a shared SQL-literal formatter backs both the diff key and the DML f2d6330 New DataCompareTests (all four categories, keyless skip, identical-in-sync, literal formatting, and a live compare→apply→re-compare round-trip) + a CLI smoke. Full suite with PGPROJ_TEST_CONNECTION.
2026-06-13 M7+ EP-PKG: project-snapshot CLI done (#142) — pgproj snapshot create writes a timestamped read-only Project_YYYYMMDD_HH-MM-SS.pgpkg of the BUILT project model under Snapshots/ (no DB); compare <A> <B> runs the unified two-way Schema Compare over snapshot/package/project specs; revert <project> <snapshot> reverse-syncs the .sql back to a snapshot (destructive removals gated by --allow-deletes, like pull); import registers an external .pgpkg as a read-only selectable compare source. Distinct from the live-DB .schema.snapshot (the bare snapshot --connection form is preserved via subcommand dispatch) 0f81c34 New ProjectSnapshotTests (create→compare self = in-sync, create→revert = no-op, compare detects a real edit) + a manual CLI smoke of all four subcommands. Full suite 25,149 / 0.
2026-06-13 M7+ EP-REFACTOR: persisted .pgrefactorlog (core) (#136) — committed {operation,objectType,oldName,newName} artifact (Refactoring/RefactorLog); the deploy planner consumes it BY DEFAULT (its presence is the opt-in) via ComparerOptions.RefactorLog, seeding the rename pre-pass so a logged table rename → ALTER … RENAME, schema move → SET SCHEMA, column rename → RENAME COLUMN (new RenameColumnChange/SetTableSchemaChange) instead of DROP+CREATE; RiskAnalyzer classifies them data-safe; CLI rename/move-schema rewrite the .sql (definition + qualified refs, word-boundary-safe) AND append the log atomically (RefactorEngine) f9feb52 New RefactorLogTests (12: consumption per kind + stale-guard + authoring rewrite + round-trip) + a live shadow-DB proof (logged rename keeps rows; no-log baseline drops them). Full suite 25,143 / 0. The log is also packed into .pgpkg (refactorlog.json, outside the source checksum) and consumed on publish-from-package. Open in #136: only expand-wildcards.
2026-06-13 M7+ EP-DEPLOY: CONCURRENTLY / lock-minimizing deploy done (#137) — ConcurrentIndexOperations option (CLI --concurrent-indexes/--minimize-locks, profile, .pgpkg): LockMinimizer rewrites index create/drop → CONCURRENTLY and named FK/CHECK adds → NOT VALID + a separate VALIDATE CONSTRAINT pass; new RunsOutsideTransaction flag partitions those steps after COMMIT and routes apply through PhasedDeployer (autocommit). RiskAnalyzer reports blocking vs non-blocking + INVALID-index note; ADD COLUMN DEFAULT version-gate comment (<PG11) 5fbd9f1 New DeployScriptConcurrent137Tests (DB-free) + a live shadow-DB round-trip proving the concurrent deploy reaches the same end-state as the transactional one (re-compare empty). Full suite green with PGPROJ_TEST_CONNECTION.
2026-06-13 M7+ EP-PROFILE: full DacDeployOptions-equivalent family done (#140) — PublishProfileOptions gains BlockOnPossibleDataLoss, granular Drop{Constraints,Indexes}NotInSource + DoNotDropObjectTypes, GenerateSmartDefaults, ScriptNewConstraintValidation (NOT VALID), AllowTableRecreation, CommandTimeout/LockTimeout (session SETs incl. PhasedDeployer), ExcludeObjectTypes — all nullable, CLI>profile>default; publish blocks on data loss by default (new exit 9 DataLossBlocked, --allow-data-loss opts out) 5fbd9f1 New DeployScriptOptions140Tests + PublishProfile round-trip/IsEmpty + a live smart-defaults round-trip; full suite 25,123 pass / 0 fail with PGPROJ_TEST_CONNECTION.
2026-06-13 M7+ EP-ANALYSIS+: PG015/PG016 added (#81) — PG015 flags uppercase identifiers (case-fold/forced-quoting footgun), PG016 flags identifiers > 63 bytes (silent truncation), on table + column names 5fbd9f1 2 new PgAnalyzerTests; documented in docs/ANALYSIS_RULES.md. #81 stays open as an ongoing rule backlog.
2026-06-12 M7+ EP-BUILD done (#135) - .pgproj SuppressWarnings + TreatWarningsAsErrors via shared BuildWarningPolicy in ContractBuilder.Analyze; --verbose structured diagnostics with file:line 861a13c 7 tests; CLI gate and in-proc editor path identical by construction.
2026-06-12 M7+ EP-PKG verify done (#138) - pgproj verify <a.pgpkg> <b.pgpkg>: model+sources+options equivalence, stamps excluded, exit 0/6, JSON+text - the DacpacVerify analogue e63417c 8 tests incl. live extract round-trip; documented in docs/CICD.md as the local reproducibility gate.
2026-06-12 M7+ EP-DEPLOYREPORT done (#141) - pgproj deploy-report / publish --report-only: apply-free planned-change report from the shared publish plan; per-op RiskAnalyzer verdicts, blocksOnDataLoss gate, pre/post + strategy, JSON+XML 221f503 Integration-tested vs real PG incl. target-unchanged guarantee; suites 25,091 Core + 28 LSP.
2026-06-12 - Analyzer fixes (#65) - PG004 matches through TEMP/OR REPLACE/UNIQUE/... modifiers; PG009 checks view bodies 9396875 11 regression cases.
2026-06-12 M7 VS experience validated in the installed product + hardened — three silent editor-chain breaks fixed (per-user MEF cache skipped by /updateconfiguration; VsBufferDetectLangSID content-type overwrite; CodeRemote base required for LSP activation); PostgreSQL classifier; live diagnostics = build verdict (reference/semantic gate + cross-file invalidation); alias-aware completion/F12/hover + column-precise definition; pgproj sync-file + in-VS file-level Sync-with-Database (diff + take-DB/push-local/cancel); CURRENT_USER validator false positive d9e75dd, 9343c5b, ca59e68, 077dbde on milestone/m7-ssdt-parity Validated by a NEW 115-scenario real-user E2E suite (FlaUI+DTE-over-ROT, Hyper-V VM, dockerized PG18 sample DB tests/sample-db): round 10 = 115/115. Engine suites green: 25,086 Core (0 skips with PGPROJ_TEST_CONNECTION) + 28 LSP. This closes the "manual F5 pass outstanding" follow-up from EP-VS #111.
2026-06-11 M7 EP-VS done (#111) — CPS .pgproj project type + templates + VS-loadable SDK (#113), .vsct/manifest (#117), four property pages as SDK CPS rules + PgProjPublishVariables--var (#114), modal Publish dialog (#115), interactive Schema Compare window (#116), pgproj sln solution grouping (#118) a1fa294, a59737e, 9be4cb2, 1e7422f on milestone/m7-ssdt-parity Headless: both VSIXes 0-warn/0-err, SDK packs with rules, full suite 22,526 pass / 0 fail (15 new SolutionGroupingTests); --var forwarding verified end-to-end via offline dry-run. Manual F5 pass in VS 2026 outstanding (README follow-ups).
2026-06-08 M7 EP-COVERAGE introspection — EXCLUDE (#98), EVENT TRIGGER tags (#104), POLICY TO roles (#103), USER MAPPING + LANGUAGE (#108), PARTITION/INHERITS (#99), index opclass/ordering (#101), expression statistics (#110), TS PARSER/TEMPLATE (#109) 8e2c387ff1de45 on milestone/m7-ssdt-parity One reader per kind (+ a parser fix for CREATE USER MAPPING); each verified vs PG18 by the deploy→read-back→reparse→re-deploy round-trip; goldens regenerated. #104 (event-trigger tags) and #101 (index ordering) also fixed latent silent phantom diffs. Full suite 25,044 pass / 0 fail.
2026-06-08 M7 EP-ANALYSIS+ done — external rule packs (#79: IPgRule+RulePackLoader) + PG006 missing-PK / PG008 untyped-numeric (#81 partial); epic #67 closed f3a7639, f734255 7 rule-pack tests + analyzer tests; doc docs/ANALYSIS_RULES.md. #81 stays open as ongoing rule backlog.
2026-06-08 M7 Complete SQLSTATE tablePgErrorCodes (262 codes / 43 classes from PostgreSQL errcodes.txt); validate/publish errors now enriched (42704 undefined_object (class 42: …)) e0a28ef 11 tests; central test DropSampleSql now drops the global afd_plpgsql language. Full suite 25,055 pass / 0 fail.
2026-06-08 M7 EP-CICD removed (user decision) — closed #71/#94/#95/#96 (not planned); #97 exit-code contract closed done; opt-in ci/azure-devops/ template removed 37309d8 Exit-code contract kept (ExitCode.cs + ExitCodeContractTests + docs/CICD.md). Backlog: base types (#102) + transforms (#108 tail) — need C functions in the PG server (labelled blocked).
2026-06-07 M7 Audit & reconcile — 4 epics (#66 TARGET, #68 PROFILE, #69 SCHEMACOMPARE, #70 TEMPLATES) + done child tasks were already on main; closed 27 redundant issues with evidence branch milestone/m7-ssdt-parity The opened M7 backlog was sourced from stale SSDT_PARITY_BACKLOG.md/COVERAGE.md; audit against the code found most of it shipped in the M-waves. Docs refreshed (this file + COVERAGE.md + backlog). Remaining open: #67 (#79,#81), #72 (#98,#99,#101–104,#108–110), #71, #111, #112.
2026-06-07 M5 #24 VS Code extension + #25 Visual Studio + #26 table designer (completes M5 & SSDT parity) merge of milestone/m5-editor-clients Two waves of worktree agents + hand-resolved merges. VS Code extension finished (LSP client, webviews, 62 vitest tests, .vsix, E2E green via a space-free-temp runner shim); VS Route-A SDK build/publish/pack validated + Route-B VSIX scaffolded; designer with engine-backed .sql round-trip (.NET 22,477 pass). Editor UIs validated to the extent runnable here.
2026-06-07 M6 #8 lazy table-constraint lists + #10 comparer fast-paths (completes M6) merge of milestone/m6-performance Two safe, BDN-measured allocation wins: parse+build All 16.16→16.06 MB/op (dashboard stage #24); comparer −19..−40%/op on a now-representative CompareBenchmarks. Corpus + goldens byte-identical; full suite green vs PG18.
2026-06-07 M4 #61 round-trip tail closed (trigger event order + function-comment types-only signature) merge of fix/61-trigger-comment-roundtrip Completes M4 (9/9). Both gaps verified fixed against PG18; round-trip guard now covers all raw kinds AllFeaturesDb exercises.
2026-06-07 M5 M5 closed: #31 delivered; GUI clients #24/#25/#26 deferred → #27 The .NET language-service foundation is the M5 deliverable; the editor GUIs are a separate toolchain effort tracked under the SSDT-parity epic.
2026-06-07 M5 #31 EP-LSP resident language service (pgproj serve) merge of milestone/m5-editor-ui New PgProj.Lsp + serve verb; STDIO LSP over the engine (debounced diagnostics = build verdict; definition/hover/completion). 16 LSP tests; corpus/goldens unaffected. Editor-client epics #24/#25/#26 remain (TypeScript/VSIX, separate toolchain).
2026-06-07 M4 #52 #53 #54 #55 #56 #57 #58 #64 + #61 (partial) — snapshots, identity diff, risk, planning, gen, incremental, options, round-trip merge of milestone/m4-diff-deploy Three dependency-ordered worktree-agent waves. PG18 suite 22,471 pass / 0 fail / 0 skip; allocation neutral; defaults byte-identical. #61 trigger + function-comment round-trip gaps remain (tracked).
2026-06-07 M3 #47 #48 #50 #51 — binding, validation, dependency graph, canonical hardening (completes M3) merge of milestone/m3-binding-validation Two worktree-agent waves (#47/#51 then #48/#50 on the bound model). PG18 suite 22,363 pass / 0 fail / 0 skip; goldens byte-identical. Parse allocation +0.35% (tightened from +1.16% — ColumnRef single-slot, trigger detail behind one ref).
2026-06-07 M2 #44 IProjectObject contract + object-kind registry (completes M2) merge of feature/44-iprojectobject-registry Contract + registry; every per-kind switch (RawObjectMeta, SchemaCompareObjectType, LiveDatabaseReader fan-out, ModelBuilder.BuildRaw) collapsed into one ObjectKindRegistry table. Golden byte-identical; PG18 suite 22,301 pass / 0 fail / 0 skip.
2026-06-07 M2 + M1 #42 #43 #45 #46 #39 (M2) + #60 #62 #63 (M1 finishers) merge of milestone/m2-semantic-core Two dependency-phased worktree-agent waves + hand-resolved integration (unified #63/#45 position tracking onto SourcePositionIndex). M1 now complete (7/7). Validated against PG18: 22,294 pass / 0 fail / 0 skip; allocation neutral. M2 5/6 — #44 remains.
2026-06-07 M1 #49 unify diagnostics + #37 throwaway-DB isolation + #36 round-trip (scoped) + #60 golden-file merge of milestone/m1-foundations M1 wave delivered via 4 worktree agents + integration fixes (aggregate identity double-schema bug). Validated against PG18: 22,217 pass / 0 fail / 0 skip; allocation footprint unchanged (16.94 MB/op). #60 golden-file done; its hash/rename tests deferred to #42. Remaining M1: #62, #63.
2026-06-07 M1 #59 deterministic raw-object ordering 2200b6c Sort model.Objects by (kind, schema, name, identity) after the parallel merge; DB-free + live regression tests; full suite green (22,195 pass against PG18).

Update contract

On every delivered milestone item, update this document in the same PR/commit that delivers it:

  1. Flip the issue's box to ✅ in §2 and add the delivery date + commit.
  2. Recompute the milestone's Done count and Status (🟡 once any item ships, ✅ when all do) in §1.
  3. Add a row to the §3 delivery log (date, milestone, item, commit, one-line note).
  4. Bump Last updated at the top.
  5. Close the GitHub issue and, when the last item ships, close the GitHub milestone.

This is a manual step performed as part of the delivery commit — no git hook, GitHub Action, or CI job runs it (the repo is validated locally; see the CI/CD hard rule in CLAUDE.md). For a complex issue, break it into smaller subtasks (the sub-bullets in §2) and check them off as you go, so progress is visible before the whole issue closes.

Sources of truth: EP-SEMCORE epic #41 · parity tracker #27 + docs/SSDT_PARITY_BACKLOG.md · perf tracker #12 + docs/parser-performance.md.