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.
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.
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.
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; unifiedDiagnosticinsrc/PgProj.Core/Diagnostics/, producers migrated, JSON contract kept stable, 8 unit tests. Follow-up #63: populate & surfaceRelated[]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 typefidelity, 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;
ThrowawayDatabaseFixtureper-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
ObjectIdentityTestsin 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,relatedon the DTO,ProjectBuildResultcarries unifiedDiagnostic)
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/CanonicalizerIdentityDiffclassifier for #53; 20 tests. Follow-up: full Phase-8 canonical form refines it (#51))*
- ✅ #43 —
PostgresVersionProfile(capabilities / catalog-queries / object-capabilities) (delivered in M2 merge; all ofLiveDatabaseReader's catalog SQL moved behind the profile, comparer asksObjectCapabilities. Per-version query/ALTER overrides are the next increment) - ✅ #44 —
IProjectObjectextensibility contract + object-kind registry (delivered; one contract per kind +ProjectObjectRegistry, and every per-kind switch collapsed into the singleObjectKindRegistrytable —RawObjectMeta,SchemaCompareObjectType.OfKind, theLiveDatabaseReaderreader fan-out, andModelBuilder.BuildRawname-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 +CatalogBuildersymbol 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/SymbolEntrywith StableId, signature overloads, reverse index) - ✅ #39 — simpler project format (delivered in M2 merge; SDK auto-includes
**/*.sql,EnableDefaultSqlItemsopt-out)
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 toSymbolEntry, exprs carryResolvedType, 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)
EP-SEMCORE Parts B+C, Phases 10–15 & 18. Complete (9/9).
- ✅ #52 — Phase 10: schema snapshots (delivered;
schema.snapshotoffline compare endpoint + staleness +pgproj snapshotCLI) - ✅ #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)
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 serveSTDIO LSP host — didOpen/didChange→debounced publishDiagnostics [verdict identical to build], definition/hover/completion; pure handlers unit-tested [16 tests]; doc atdocs/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 topgproj serve, projects tree, commands, Publish + Schema-Compare webviews, diagnostics→Problems. 62 vitest tests, tsc/eslint clean,.vsixpackages; E2E green [8 pass/1 pending] via a space-free-temp runner shim.) - ✅ #25 — EP-VS: Visual Studio experience (Route A:
.pgprojbuilds/publishes viaPgProj.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-tableverbs —.sqlgeneration stays inSqlEmitter; byte-stable round-trip proven over the corpus.)
Editor UIs are validated to the extent runnable here (unit +
tsc+.vsixpackage + the .NET engine/round-trip layer
- the VS Code E2E); a full VSIX build + VS Apex UI tests need the Visual Studio SDK host.
Open items from the perf/deploy-sync tracker (#12). Benchmark-gated (bytes/op); corpus byte-identical. Complete (2/2).
- ✅ #8 — reduce
ModelBuilderallocation (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; representativeCompareBenchmarks+ comparer fast-paths — −19% to −40% allocated/op across the compare path; behavior-preserving.)
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):
- ✅ #66 — EP-TARGET: target-platform enforcement —
TargetVersionAnalyzer+PgVersionCapabilities/SupportedFeaturestable +PGV###; gate wired into build/publish/validate. TestsTargetVersionTests/VersionProfileTests. - ✅ #68 — EP-PROFILE: publish profiles —
Deployment/PublishProfile.cs(secret-whitelisted) +profile create+--profile(CLI>profile>default). TestsPublishProfileTests. - ✅ #69 — EP-SCHEMACOMPARE: unified two-way
Comparison/SchemaCompare.cs+ selectableSchemaChangeSet+--output diff.json+--exclude. TestsSchemaCompareTests. - ✅ #70 — EP-TEMPLATES:
Templates/*+add/new projectverbs +dotnet newpack attemplates/. TestsTemplateTests/TemplateIntegrationTests. - ✅ #67 — EP-ANALYSIS+: config (#77),
--rule(#78), SARIF (#80), external rule packs (#79 —IPgRule+RulePackLoader), and rules PG006/PG008. Docdocs/ANALYSIS_RULES.md. Epic closed; #81 (grow the rule set) stays open as ongoing backlog.
Delivered:
- ✅ #72 — EP-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
TOroles (#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.) - ✅ #111 — EP-VS: Visual Studio Route B VSIX + slngen grouping — all six children #113–#118 delivered 2026-06-11: real CPS
.pgprojproject type + templates + VS-loadable SDK (#113),.vsct/manifest wiring (#117), four property pages asPgProj.SdkCPS rules incl.PgProjPublishVariables→--var(#114), modal Publish dialog (#115), interactive Schema Compare window with pickers/checkable diff/Generate Script/Apply (#116), andpgproj sln new|add|listsolution 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):
- ⛔ #112 — EP-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.
- ❌ #71 — EP-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-inci/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.
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
.pgpkgpackage references — delivered 2026-06-24. All three children done:- ✅ #147 —
dotnet packa project's built.pgpkginto a consumable.nupkg(pgpkg/<Name>.pgpkg, id/version) —7344705. - ✅ #148 —
ReferenceResolverresolves aPackageReferencefrom 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.
- ✅ #147 —
- ✅ #150 (child of #139 EP-UNITTEST) —
pgproj teststub 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-embeddeddata/COPY section (BACPAC-analogue variant) — delivered 2026-06-24,0a06a3d. Completes EP-EXTRACT #134. Solved the COPY-vs-GENERATED ALWAYS AS IDENTITYblocker by relaxing the column toBY DEFAULTaround the load; verified end-to-end against live PG18. - ✅ #152 (child of #136 EP-REFACTOR) —
expand-wildcardscommand (SELECT *→ explicit columns, model-resolved) — delivered 2026-06-24,7809b6d. Completes EP-REFACTOR #136.
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.DependencyGraph → SchemaComparer.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 STORED — GeneratedStored.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) |
8e2c387…ff1de45 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 table — PgErrorCodes (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). |
On every delivered milestone item, update this document in the same PR/commit that delivers it:
- Flip the issue's box to ✅ in §2 and add the delivery date + commit.
- Recompute the milestone's
Donecount andStatus(🟡 once any item ships, ✅ when all do) in §1. - Add a row to the §3 delivery log (date, milestone, item, commit, one-line note).
- Bump Last updated at the top.
- 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.