feat: complete workflow-plugin-audit-chain (modules + steps + integration) — PR 4#3
Merged
feat: complete workflow-plugin-audit-chain (modules + steps + integration) — PR 4#3
Conversation
- Add dsn field to LedgerConfig proto + three new anchor provider config proto messages (OpenTimestampsProviderConfig, GitAnchorProviderConfig, SigstoreProviderConfig); regenerate gen/audit.pb.go. - modules/ledger.go: LedgerModule typed with LedgerConfig; Init opens Postgres via sql.Open, creates chain.Appender, registers in ledger registry keyed by partition name; Stop unregisters + closes DB. - modules/anchor_provider.go: anchorProviderModule wraps each provider; typed factory functions (NewOpenTimestampsProviderModule, NewGitAnchorProviderModule, NewSigstoreProviderModule) construct the underlying provider and register it on Init. - 14 unit tests; all pass without a running Postgres instance. - internal/plugin.go: implement TypedModuleProvider (CreateTypedModule) so the gRPC server uses the typed proto path — zero map[string]any at the module creation boundary. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Critical: - NewLedgerModule now validates config.Name is non-empty; silently registering under key "" would corrupt the ledger registry. Important: - Fix AuditChainPlugin doc comment: remove false TypedStepProvider claim (Task 14 adds that interface). Add compile-time assertion `var _ sdk.TypedModuleProvider = (*AuditChainPlugin)(nil)`. - Add 5 CreateTypedModule tests (the primary gRPC path was unexercised): valid LedgerConfig, nil config, mismatched anypb type, deferred providers, unknown type. Minor: - TestLedgerModule_StopUnregisters: add missing t.Cleanup guard. - LedgerModule.Init: guard against double-call to prevent DB pool leak. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements Task 14. All step handlers use sdk.TypedStepProvider (zero map[string]any). Key changes: - proto/audit.proto: add ledger field (5) to PollAnchorConfirmationRequest so poll handlers can retrieve the right *sql.DB from the registry. - gen/audit.pb.go: regenerated. - modules/ledger.go: add RegisterDB/GetDB/UnregisterDB registry; Init() and Stop() register/unregister both the chain.Appender and *sql.DB. - modules/anchor_provider.go: add AnchorProviderNames() for step.audit.anchor "all configured providers" fallback. - steps/: new package — AppendHandler, VerifyHandler, MerkleRootHandler, AnchorHandler, PollAnchorConfirmationHandler (swallow-transient-errors contract), ProofHandler (RFC 6962 inclusion proof), PublicReceiptHandler (verifiable receipt JSON + SHA256 hash, optional field redaction). - internal/plugin.go: wire TypedStepProvider; stepFactories slice maps each step type to its sdk.NewTypedStepFactory; legacy CreateStep path returns "not yet implemented via legacy path" for known types. - internal/plugin_test.go: TestTypedStepTypes_Declared, TestCreateTypedStep_UnknownType_ReturnsError, TestCreateTypedStep_KnownType_ReturnsInstance. All packages pass: GOWORK=off go test ./... -race -count=1. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Issue 1 — poll_anchor_confirmation transient/hard-error test coverage: - steps/testutil_test.go: add mockAnchorProvider (controls Verify return) and openFakeDB / fakeSQLDriver (fake database/sql driver returning confirmation="pending" for any SELECT without a real Postgres instance). - steps/poll_anchor_confirmation_test.go: add TestPollAnchorConfirmationHandler_TransientError_Swallowed — asserts swallowed=true, transitioned=false, error_message set, no gRPC error. Add TestPollAnchorConfirmationHandler_HardError_PropagatesGRPC — asserts non-nil error when provider.Verify returns a hard error. Issue 2 — public_receipt pseudonym format and replace-not-delete: - ApplyRedactions (now exported for direct testing): assigns "contributor_N" labels (N increments per unique original value within the receipt scope); REPLACES the field value with the pseudonym string instead of deleting the field; deduplicates: identical originals share one label. - steps/public_receipt_test.go: add TestApplyRedactions_TwoDistinctFields, TestApplyRedactions_DuplicateOriginalValue, TestApplyRedactions_NoRedactFields. Issue 3 — map[string]any in public_receipt.go: - Replaced map[string]any receipt document and anchorRecordSlice with typed structs: receiptDocument, receiptEntry, receiptMerkleProof, receiptAnchor. Zero map[string]any in the package. All packages pass: GOWORK=off go test ./... -race -count=1. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- chain: Append/AppendTx return DB-assigned createdAt via RETURNING created_at; steps/append.go propagates DB timestamp to AppendResponse (not app clock) - chain: update all call sites in append_test.go to 4-return destructuring - poll_anchor_confirmation: guard against downgrade with forward-only confirmationOrder map (pending<confirmed<finalized) - anchor: INSERT ... ON CONFLICT (ledger,provider,range_start,range_end) DO NOTHING for idempotent re-runs; matching unique index added to 004_indexes.sql + down Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- TestIntegration_BinaryBuilds: shells out to go build to verify the cmd/workflow-plugin-audit-chain binary compiles and links end-to-end - TestIntegration_AllStepsAreTyped: creates all 7 step types and verifies Execute() returns the typed-step dispatch error, proving each is a TypedStepInstance (not a legacy step) - TestIntegration_LedgerModuleLifecycle: exercises the full Init→Start→Stop lifecycle, verifying ledger+DB registries are populated after Init and cleared after Stop; also verifies double-Init guard - TestIntegration_AnchorProviderModule_OpenTimestamps: lifecycle smoke test for the OTS anchor provider module (no network calls on Init) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…le, proof) integration_test.go at the module root exercises the full audit-chain scenario through the plugin factory chain: 1. Spins up ephemeral Postgres 16 via testcontainers + applies all 4 migrations. 2. Declares audit.ledger via internal.NewPlugin().CreateTypedModule — exercises the typed module factory wiring in internal/plugin.go. 3. Appends 5 entries via step.audit.append; verifies sequence 1-5 and 64-char hashes. 4. Verifies chain integrity over all 5 entries via step.audit.verify; expects Valid=true, EntriesVerified=5. 5. Computes Merkle root over entries 1-5 via step.audit.merkle_root; cross-checks against independently computed chain.MerkleRoot. 6. Records a mock audit_anchors row for range 1-5 with the computed root. 7. Retrieves and cryptographically verifies the Merkle inclusion proof for entry 3 via step.audit.proof + chain.VerifyInclusion. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…sport
Addresses all 3 spec-reviewer rejection issues:
1. Delete internal/integration_test.go (wrong location — was internal package,
not repo root).
2. Root integration_test.go now includes the full audit chain scenario:
append-5, verify, Merkle root, cross-check, mock anchor, inclusion proof
for entry 3, and chain.VerifyInclusion cryptographic check.
3. All step executions go through real gRPC proto serialisation:
- testGRPCPlugin wraps go-plugin and dispenses pb.PluginServiceClient
directly (bypassing *ext.PluginClient's unexported fields).
- buildBinary/startPlugin compile the actual binary and start it as a
subprocess via goplugin.NewClient + ext.Handshake.
- Each request is packed as anypb.Any, sent over TCP gRPC to the subprocess,
and unpacked from the typed response — genuine wire serialisation.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Important #1: add testing.Short() guard at top of TestE2E_AuditChainScenario so Docker-dependent test skips cleanly under `go test -short ./...`; remove redundant guard from buildBinary (test-level guard fires first). - Important #3: log StopModule errors in t.Cleanup instead of discarding with blank identifier. - Minor #4: run `GOWORK=off go mod tidy` — promotes testcontainers-go and testcontainers-go/modules/postgres from // indirect to direct deps. Important #2 (direct step handler calls) was already resolved in the prior commit (1aa6765) which rewrote the test to use goplugin.NewClient subprocess + pb.PluginServiceClient gRPC calls. Code-reviewer reviewed an earlier commit. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…iders plugin.contracts.json: rewrote all 12 descriptors with required "kind", "type", and "mode": "strict_proto" fields (previously all had neither "kind" nor "mode", causing the wfctl validator to skip every descriptor and report 14 coverage-gap errors). plugin.json: remove audit.anchor_provider.ethereum and audit.anchor_provider.aws_qldb from capabilities.moduleTypes — those providers are not implemented and must not appear in the advertised contract surface. Validator now passes: OK workflow-plugin-audit-chain v0.1.0 (plugin.json) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The trigger.audit.entry_appended contract referenced a non-existent proto type workflow.plugin.audit.v1.AuditEntry. The actual message is workflow.plugin.audit.v1.Entry (proto/audit.proto line 242). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR delivers the complete audit-chain plugin implementation across three tasks, and closes the
wfctl-strict-contractsCI gate that was failing on the admin-merged PR 1 and PR 3. All five step types now carrySTRICT_PROTOcontracts inplugin.contracts.json, satisfying the gate that checks for fully-typed proto dispatch end-to-end.What's included
Task 13 —
audit.ledger+audit.anchor_provider.*modules (commitsfefd185,fd59afe)modules.LedgerModule(audit.ledger): opens a pgx connection pool, registerschain.Appenderand*sql.DBin global ledger/DB registries, full Init/Start/Stop lifecycle with double-init guard.modules.OpenTimestampsProviderModule(audit.anchor_provider.opentimestamps): registers an OTS anchor provider, no network calls on Init.modules.GitProviderModule(audit.anchor_provider.git): registers a Git-commit anchor provider.modules.SigstoreProviderModule(audit.anchor_provider.sigstore): registers a Sigstore/Rekor anchor provider.Task 14 — 7 step types with strict proto contracts (commits
4ef1f90,6e4d1b5,9fc52cc)Seven steps, each wired via
sdk.NewTypedStepFactorywithSTRICT_PROTOcontracts:step.audit.appendAppendHandlerAppendRequest→AppendResponsestep.audit.verifyVerifyHandlerVerifyRequest→VerifyResponsestep.audit.merkle_rootMerkleRootHandlerMerkleRootRequest→MerkleRootResponsestep.audit.anchorAnchorHandlerAnchorRequest→AnchorResponsestep.audit.proofProofHandlerProofRequest→ProofResponsestep.audit.poll_anchor_confirmationPollAnchorConfirmationHandlerPollAnchorConfirmationRequest→PollAnchorConfirmationResponsestep.audit.public_receiptPublicReceiptHandlerPublicReceiptRequest→PublicReceiptResponseKey correctness invariants:
AppendHandlerusesRETURNING created_at— timestamps come from the DB clock, not the Go process.AnchorHandlerinserts withON CONFLICT (ledger, provider, range_start, range_end) DO NOTHING— anchor step is idempotent.PollAnchorConfirmationHandlerenforces a forward-only ordering guard (pending → confirmed → finalized).Task 15 — gRPC plugin entrypoint + E2E integration test (commits
70937e3,1aa6765,cf88b67)cmd/workflow-plugin-audit-chain/main.gocallssdk.Serve(internal.NewPlugin()).internal/plugin.goregisters all 4 module types and 7 step types asTypedModuleProvider+TypedStepProvider, with compile-time interface assertions.integration_test.go(repo root,package integration_test): end-to-end scenario over real gRPC transport:goplugin.NewClient+ext.Handshake.CreateModule → InitModule → StartModulewithanypb.Any(LedgerConfig)over TCP gRPC.CreateStep + ExecuteStep(AppendRequest)— entry hashes collected fromAppendResponse.ExecuteStep(VerifyRequest)— assertsvalid=true, entries_verified=5.ExecuteStep(MerkleRootRequest)— cross-checks root againstchain.MerkleRoot(entryHashes).ExecuteStep(ProofRequest)retrieves inclusion proof for entry 3.chain.VerifyInclusion(hash3, merklePath, root)— cryptographic closure.go test -short(no Docker required).Test plan
GOWORK=off go build ./...— cleanGOWORK=off go test -short ./...— 146 tests pass, E2E skippedGOWORK=off go test -v -count=1 -timeout=300s .— E2E integration test passes (~20s with Docker + binary build)go test -short .—--- SKIP: TestE2E_AuditChainScenario(Docker guard confirmed)🤖 Generated with Claude Code