Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/rfc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,6 @@ All "implemented" modules are published on nuget.org at `0.1.0-preview.6` (the c
| [qorpe-ui](qorpe-ui.md) | @qorpe/ui — the family UI kit extraction (platform-sdk D5) | **implemented** (step 6.0, 2026-08-07): the kit lives in [qorpe/ui](https://github.com/qorpe/ui), publishes to npm via OIDC trusted publishing, and BOTH consumers run on the published package — `ui/kit` is deleted from this repo |
| [qorpe-sync](qorpe-sync.md) | qorpe.sync (separate private repo) — the migration & co-existence product module (ADR-0012's own named example): CDC capture, ordered replayable stream, adapter-based versioned mapping, event-id reconciliation composing db-compare | **accepted** (owner, 2026-08-18) — v0 scope is engagement-shaped (§5.1 by-product rule); build scheduled, two-store rig first; the second-product-repo ordering is an owner decision recorded in RFC D5 |
| [goldpath-approvals](goldpath-approvals.md) | Goldpath.Approvals — human approval workflows: maker-checker, four-eyes, amount-laddered authority, delegation, escalation, worklist; definitions as versioned data | **accepted** (owner, 2026-08-18) — build started same day by owner decision; core + 20 tests + manifest/CLI/template wiring + EF store landed; console federation and the adopter proof stay open (T21) |
| [goldpath-approvals-v2](goldpath-approvals-v2.md) | The product-proven ladder deltas: withdraw, quorum rungs, distinct-eyes, mandatory rejection reason, reject→resubmit chain — additive, non-breaking | **proposed** (2026-08-26; source: the api-portal product's engine, exam-proven) |
| [goldpath-approvals-v2](goldpath-approvals-v2.md) | The product-proven ladder deltas: withdraw, quorum rungs, distinct-eyes, mandatory rejection reason, reject→resubmit chain — additive, non-breaking | **accepted** (owner, 2026-08-26) — implemented 2026-08-27: withdraw, quorum rungs, chain-wide distinct-eyes, mandatory rejection reason, resubmit chain; 17 new facts + the package's own mutation gate |
| [goldpath-fileexchange](goldpath-fileexchange.md) | Goldpath.FileExchange — file-based integration rails as a unit: scheduled pick-up/drop, format contracts, idempotent `(file,line)` ingestion, quarantine, reprocessing, archival, outbound | **accepted** (owner, 2026-08-18) — build started same day by owner decision; core + 9 tests (planted-fault rig + EF ledger) + manifest/CLI/template wiring landed; transports, console federation and the adopter proof stay open (T22) |
| [specanchor-composition](specanchor-composition.md) | specanchor (separate repo) — the transformation package's deterministic core, composed per foundation §9/§5.1 | **ACCEPTED** (2026-08-16, #165) — the toolchain is real ([qorpe/specanchor](https://github.com/qorpe/specanchor), Apache-2.0; 74 tests, mutation 94.8%, first skill eval 7/7); the composition proof it owes is `open-threads.md` T20 (the rehearsal) |
17 changes: 16 additions & 1 deletion docs/rfc/goldpath-approvals-v2.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# RFC: Approvals v2 — the product-proven rules the ladder engine is missing

Status: **Proposed** (owner decision pending — per the constitution, code follows approval).
Status: **Accepted** (owner, 2026-08-26) — implemented 2026-08-27: all six rules landed in
`Goldpath.Approvals` with the engine/store tests and the package's own mutation gate.
Source of truth for the deltas: the api-portal product's approval engine, where every rule
below shipped, survived contract tests and ran live (multi-stage chains proven 2026-08-24;
the whole engine exercised daily by three exams).
Expand Down Expand Up @@ -50,3 +51,17 @@ the CorPay sample's approval story exercises withdraw + quorum; mutation gate ho

Approve → implementation lands as one PR with tests and ledger updates. Reject/amend →
this file records why, per the ledger discipline.

## 6. Implementation record (2026-08-27)

All six deltas landed additively: `Withdrawn` status + `WithdrawAsync` (requester-only,
`NotRequester` refusal), `RequiredApprovals` on the rung with per-rung signature quorum,
chain-wide distinct-eyes (`AlreadySigned` — held across escalation), `ReasonRequired` on
blank rejection, `ResubmitAsync` with `SupersedesId` and cross-referenced trails, and the
store seam's `AddSignatureAsync`/`GetSignaturesAsync` on both stores (the EF store maps a
`GoldpathApprovalSignatures` table). Refusal-as-value stayed the decision-verb contract;
`ResubmitAsync` throws on misuse like `RequestAsync`/`DelegateAsync` (creation verbs).
Proof: 17 new deterministic facts (engine + EF restart/round-trip) beside the original 20,
and the package joined the mutation gate (`stryker/Goldpath.Approvals.json`, break 70).
The CorPay approval story remains with T21's adopter proof — the sample does not compose
approvals yet, so wiring it there would be a demo invented for the test's sake.
2 changes: 1 addition & 1 deletion docs/strategy/open-threads.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ pending forever.
| T17 | **Per-package ops packs for the three floor packages** (Messaging, Data, ApiDefaults) | Their RFC §6 sections describe dashboards (consumer lag, outbox backlog, EF query duration, deprecated-version traffic) that are NOT packaged — the signals reach OTel through ServiceDefaults, whose ops pack is what an adopter gets. Found by an audit 2026-08-09; the RFC sections now say so | the first adopter who asks for a floor dashboard, OR the first incident whose triage needed one | the pack ships with the same shape the five module packs have (runbook + Grafana JSON), and the RFC §6 correction note is removed |
| T18 | **The MassTransit 8.x exit** — the watch that keeps option A honest | [goldpath-messaging-exit](../rfc/goldpath-messaging-exit.md) decided **A: stay pinned on 8.5.10** (Apache-2.0), because a move is a MAJOR version, not an internal swap — measured, not assumed. The publish seam already shipped (`IIntegrationEventPublisher` + GP0404), so a generated app's COMMAND HANDLERS no longer name the library; its CONSUMERS still do, deliberately | **any one of four**: an unpatched CVE in the 8.x line · the vendor's v8 maintenance window closing (~end 2026 per their own statements) · an adopter's licensing constraint · a customer requiring a bus we do not compose | the five proofs in that RFC §7 — outbox atomicity on the new transport · tenant+correlation headers still propagate (H4) · GP0401-0403 still mean something · the golden-manifest matrix green on every broker-bearing shape · CorPay migrated with a written guide an adopter could actually follow |
| T20 | **The specanchor rehearsal** — the transformation composition's proof ([specanchor-composition](../rfc/specanchor-composition.md)) | specanchor's own chain is proven on its rig (74 tests, mutation 94.8%, first skill eval 7/7), but the COMPOSITION — §9's method running on specanchor's machinery with Goldpath as target — has never run end to end; per the RFC's D4/D5, no profile code is written before it does | the fake-legacy → Goldpath migration rehearsal is scheduled (before the factoring engagement's PoC) | Discovery Zero → dual-track slices → approved specs → `goldpath new` target → parity with Mockifyr equalization → db-compare reconciliation → cutover evidence bundle, with the Discovery Zero playbook written from the rehearsal's diary; only then do ai-sdlc-status's `reverse-engineer`/`differential-test` rows leave NOT BUILT |
| T21 | **`Goldpath.Approvals`** — human approval workflows: maker-checker, four-eyes, amount-laddered authority, delegation, escalation, and the task worklist/inbox that feeds them | A Ring B candidate surfaced by the factoring-class engagement: SIX of its twelve common processes run approvals over e-mail today, and the amount ladder (expert → deputy → manager → GM) is domain-agnostic across banking/insurance/telco. Distinct from the T19 saga non-goal — these are HUMAN approval chains, not process orchestration. The RFC is ACCEPTED and the build STARTED 2026-08-18 by explicit owner decision (the trigger pulled early — recorded in the RFC header): `Goldpath.Approvals` core landed with 17 deterministic tests, manifest key + CLI recipe wired. 2026-08-18 (later the same day) the database-backed store landed (EF on the app's own DbContext, proven on real storage) and the template flag shipped (a generated app with the flag BUILDS against the train). Open before the thread closes: console federation, dashboard JSON — and the adopter proof below | The engagement's implementation phase needs its first systematized approval flow (the §5.1 by-product rule: born inside a real deliverable, never as a third front) | The RFC through the eight sections; the four Ring B criteria confirmed in review; one real amount-laddered approval flow from the adopter runs end to end on the module, with its audit trail **2026-08-26 addendum:** the api-portal product built the sibling governed-change engine and proved SIX rules the ladder lacks — recorded as [goldpath-approvals-v2](../rfc/goldpath-approvals-v2.md), proposed, code awaits the owner's yes |
| T21 | **`Goldpath.Approvals`** — human approval workflows: maker-checker, four-eyes, amount-laddered authority, delegation, escalation, and the task worklist/inbox that feeds them | A Ring B candidate surfaced by the factoring-class engagement: SIX of its twelve common processes run approvals over e-mail today, and the amount ladder (expert → deputy → manager → GM) is domain-agnostic across banking/insurance/telco. Distinct from the T19 saga non-goal — these are HUMAN approval chains, not process orchestration. The RFC is ACCEPTED and the build STARTED 2026-08-18 by explicit owner decision (the trigger pulled early — recorded in the RFC header): `Goldpath.Approvals` core landed with 17 deterministic tests, manifest key + CLI recipe wired. 2026-08-18 (later the same day) the database-backed store landed (EF on the app's own DbContext, proven on real storage) and the template flag shipped (a generated app with the flag BUILDS against the train). Open before the thread closes: console federation, dashboard JSON — and the adopter proof below | The engagement's implementation phase needs its first systematized approval flow (the §5.1 by-product rule: born inside a real deliverable, never as a third front) | The RFC through the eight sections; the four Ring B criteria confirmed in review; one real amount-laddered approval flow from the adopter runs end to end on the module, with its audit trail **2026-08-26 addendum:** the api-portal product built the sibling governed-change engine and proved SIX rules the ladder lacks — recorded as [goldpath-approvals-v2](../rfc/goldpath-approvals-v2.md); owner approved 2026-08-26 and the six rules LANDED 2026-08-27 (withdraw, quorum rungs, chain-wide distinct-eyes, mandatory rejection reason, resubmit chain, signature store seam — 17 new facts, mutation-gated). Still open here: console federation, dashboard JSON, and the adopter proof |
| T22 | **`Goldpath.FileExchange`** — file-based integration rails: scheduled pick-up/drop, validation, idempotent ingestion, reprocessing, archival | Banking file rails (nightly registry FTP batches, 2×/day bank status files, MT940, CSV extraction contracts) recur in THREE of the engagement's common processes; Jobs+Bulk cover pieces but not the rail as a unit. The RFC is ACCEPTED and the build STARTED 2026-08-18 by explicit owner decision (the trigger pulled early — recorded in the RFC header): `Goldpath.FileExchange` core landed with the 7-test planted-fault rig, manifest key + CLI recipe wired. 2026-08-18 (later the same day) the database-backed ledger landed (EF on the app's own DbContext, proven on real storage) and the template flag shipped (a generated app with the flag BUILDS against the train). Open before the thread closes: transport adapters, console federation, dashboard JSON — and the adopter proof below | The first adopter integration that is file-based and bidirectional enters an implementation backlog | One real file rail runs with replay + idempotency + failure reprocessing + its ops runbook, per the module ops-pack shape |
| T23 | **Factoring industry pack + vertical template** — glossary seeds (with false-friend notes), edge-case catalogs, regulatory constraint register skeleton, accounting event dictionary patterns, DMN pricing patterns, integration blueprints (spec + Mockifyr stubs), and a `goldpath new` template pre-setting the domain's bounded-context skeleton | Foundation §6.3's industry-pack layer is the sanctioned home for domain knowledge — the core stays domain-free. The pack is DISTILLED from engagement work, never built up front; the leakage red line holds: public/regulatory knowledge + generalized patterns only, client IP contractually excluded | The engagement's first phase proves the patterns in production AND a second factoring-class prospect appears | The template stands up the domain skeleton via `goldpath new`; the pack's seeds pass the same eval discipline as skills; zero client-confidential content, confirmed by written IP review |

Expand Down
52 changes: 52 additions & 0 deletions packages/Goldpath.Approvals/GoldpathApprovalEfStore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,25 @@ public sealed class GoldpathApprovalDelegationRow
public DateTimeOffset Until { get; set; }
}

/// <summary>One collected quorum signature as a row (the record itself has no key; the row carries one).</summary>
public sealed class GoldpathApprovalSignatureRow
{
/// <summary>Row id.</summary>
public long Id { get; set; }

/// <summary>The request the signature belongs to.</summary>
public Guid RequestId { get; set; }

/// <summary>Who signed.</summary>
public string SignedBy { get; set; } = "";

/// <summary>The rung the signature counts for.</summary>
public string Role { get; set; } = "";

/// <summary>When it was signed (UTC).</summary>
public DateTimeOffset At { get; set; }
}

/// <summary>Model mapping for the database-backed approval store.</summary>
public static class GoldpathApprovalModelExtensions
{
Expand Down Expand Up @@ -62,6 +81,15 @@ public static ModelBuilder AddGoldpathApprovalModel(this ModelBuilder modelBuild
e.HasIndex(x => x.Until);
});

modelBuilder.Entity<GoldpathApprovalSignatureRow>(e =>
{
e.ToTable("GoldpathApprovalSignatures");
e.HasKey(x => x.Id);
e.Property(x => x.SignedBy).HasMaxLength(128);
e.Property(x => x.Role).HasMaxLength(128);
e.HasIndex(x => x.RequestId);
});

return modelBuilder;
}
}
Expand Down Expand Up @@ -137,4 +165,28 @@ public async Task<IReadOnlyList<GoldpathApprovalDelegation>> GetDelegationsAsync
return rows.Where(r => r.Until > now)
.Select(r => new GoldpathApprovalDelegation(r.From, r.To, r.Until)).ToList();
}

/// <inheritdoc />
public async Task AddSignatureAsync(GoldpathApprovalSignature signature, CancellationToken cancellationToken = default)
{
await using var scope = _scopes.CreateAsyncScope();
var db = scope.ServiceProvider.GetRequiredService<TContext>();
db.Add(new GoldpathApprovalSignatureRow { RequestId = signature.RequestId, SignedBy = signature.SignedBy, Role = signature.Role, At = signature.At });
await db.SaveChangesAsync(cancellationToken);
}

/// <inheritdoc />
public async Task<IReadOnlyList<GoldpathApprovalSignature>> GetSignaturesAsync(Guid requestId, CancellationToken cancellationToken = default)
{
await using var scope = _scopes.CreateAsyncScope();
var db = scope.ServiceProvider.GetRequiredService<TContext>();
// Ordered client-side for the same provider reason as the delegations above —
// DateTimeOffset ordering does not translate on SQLite, and one request's
// signatures are few by nature.
var rows = await db.Set<GoldpathApprovalSignatureRow>().AsNoTracking()
.Where(x => x.RequestId == requestId)
.ToListAsync(cancellationToken);
return rows.OrderBy(r => r.At)
.Select(r => new GoldpathApprovalSignature(r.RequestId, r.SignedBy, r.Role, r.At)).ToList();
}
}
Loading
Loading