Skip to content

Conversation

@gfyrag
Copy link
Contributor

@gfyrag gfyrag commented Oct 5, 2025

Integrate in logs.
Integrate in events.

@gfyrag gfyrag requested a review from a team as a code owner October 5, 2025 21:29
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 5, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds per-ledger schema support: new V2 API endpoints (insert/get/list), schema models and persistence, schema-aware validation and propagation through logs, controller, bulk processing and storage, inserted-schema events, and client SDK/docs updates.

Changes

Cohort / File(s) Summary
Schema Core & Chart
internal/schema.go, internal/chart.go
New Schema/SchemaData models, bun-backed Schema, ChartOfAccounts types, JSON (un)marshal, account resolution and posting validation.
API v2 Handlers & Routes
internal/api/v2/controllers_schema_insert.go, internal/api/v2/controllers_schema_read.go, internal/api/v2/controllers_schema_list.go, internal/api/v2/routes.go, internal/api/v2/views.go
New handlers and route registrations: POST /schema/{version}, GET /schema/{version}, GET /schema (paginated); rendering helper.
Controller Surface & Processing
internal/controller/ledger/controller.go, internal/controller/ledger/controller_default.go, internal/controller/ledger/controller_with_events.go, internal/controller/ledger/controller_with_traces.go, internal/controller/ledger/controller_with_too_many_client_handling.go, internal/controller/system/*
Added InsertSchema, GetSchema, ListSchemas; InsertSchema type; schema enforcement mode option; wired schema through controller layers, events, traces, retries, and system wiring.
Log & Processing Flow
internal/log.go, internal/controller/ledger/log_process.go, internal/controller/ledger/log_process_test.go
New InsertedSchema log type; logs include SchemaVersion; log processing resolves/validates schemas and propagates schema into transaction processing.
Store & Storage
internal/controller/ledger/store.go, internal/storage/ledger/schema.go, internal/storage/ledger/store.go, internal/storage/ledger/resource_schemas.go, internal/storage/ledger/accounts.go, internal/storage/ledger/transactions.go, internal/storage/ledger/*_test.go
Store interface and implementations: Insert/Find/List schemas, FindLatestSchemaVersion, schema-aware CommitTransaction and UpsertAccounts, Schemas() paginated resource; tests updated for new signatures.
Database Migration
internal/storage/bucket/migrations/44-add-schema/up.sql, internal/storage/bucket/default_bucket.go
Adds schemas table, adds INSERTED_SCHEMA log enum, logs.schema_version column, updates compute_hash to include schemaVersion; bump minimal schema version.
Bulk Processing & Controllers
internal/api/bulking/bulker.go, internal/api/v2/controllers_bulk.go, internal/api/bulking/bulker_test.go
Bulker.Run/processElement now accept/propagate schemaVersion; controller passes schemaVersion from query parameters.
Errors & Validation
internal/errors.go, internal/controller/ledger/errors.go, internal/api/common/errors.go
New error types and mappings: ErrInvalidSchema, ErrInvalidAccount, ErrSchemaNotFound, ErrSchemaValidationError, ErrSchemaNotSpecified, ErrSchemaAlreadyExists and API error codes.
Events & Messaging
internal/bus/listener.go, pkg/events/events.go, pkg/events/message.go, pkg/events/*
New event constant INSERTED_SCHEMA, event payload InsertedSchema, listener publish hook and event generation wiring.
Client SDK & Docs
pkg/client/v2.go, pkg/client/models/components/*, pkg/client/models/operations/*, pkg/client/docs/models/*, pkg/client/docs/sdks/v2/README.md, docs/api/README.md
Generated client operations InsertSchema/GetSchema/ListSchemas; new models (V2Schema, V2SchemaData, cursors), schemaVersion added to relevant requests; many docs pages added/updated.
Mocks & Tests
internal/api/*/mocks_ledger_controller_test.go, internal/controller/ledger/*_generated_test.go, internal/controller/ledger/store_generated_test.go, internal/api/v2/*_test.go, test/e2e/*
Mocks extended for schema methods; unit, integration, and e2e tests added/updated (handlers, storage, bulk, idempotency).
Misc / Call-site Signatures
internal/account.go, internal/storage/ledger/*_test.go, internal/storage/ledger/*
Signature updates propagate (e.g., CommitTransaction, UpsertAccounts, Parameters.SchemaVersion); tag and minor formatting updates across files.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API as V2 Handler
    participant Ctrl as Controller
    participant LP as LogProcessor
    participant Store
    participant DB as Database
    participant Bus as EventBus

    Client->>API: POST /v2/{ledger}/schema/{version} (SchemaData, IdempotencyKey?)
    API->>Ctrl: InsertSchema(ctx, {Version, Data, IdempotencyKey})
    Ctrl->>LP: forgeLog InsertedSchema
    LP->>Store: FindLatestSchemaVersion(ctx)
    Store->>DB: SELECT ... ORDER BY created_at DESC LIMIT 1
    DB-->>Store: latest_version
    alt version exists
        Store-->>LP: ErrSchemaAlreadyExists
        LP-->>Ctrl: error
        Ctrl-->>API: 409 Conflict
    else create new schema
        LP->>Store: InsertSchema(ctx, schema)
        Store->>DB: INSERT INTO schemas (...)
        DB-->>Store: created_schema
        LP->>Store: InsertLog(ctx, INSERTED_SCHEMA, schemaVersion)
        Store->>DB: INSERT INTO logs (...)
        DB-->>Store: log_row
        LP->>Store: Commit()
        Store->>DB: COMMIT
        LP-->>Ctrl: (log, insertedSchema)
        Ctrl->>Bus: listener.InsertedSchema(ledger, schema)
        Bus->>Bus: Publish EventTypeInsertedSchema
        Ctrl-->>API: success (204) + Idempotency-Hit header if replay
        API-->>Client: 204 No Content
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas requiring extra attention:

  • internal/chart.go — account resolution, pattern matching, JSON roundtrip correctness.
  • internal/controller/ledger/log_process.go — schema lookup, enforcement-mode branching, validation error handling.
  • internal/storage/ledger/accounts.go — SQL batching/upsert, default metadata derivation from schema.
  • Signature-wide changes — ensure all call sites updated for CommitTransaction, UpsertAccounts, and Parameters.SchemaVersion.
  • Migration and compute_hash change — verify backward-compatibility and existing logs handling.
  • Generated client models and docs — check JSON keys with dot-prefixed fields (e.g., ".pattern", ".self") and cursor encoding/decoding.

Poem

🐇 I nibbled through ledgers, found schema seeds to sow,
Charts unfurled their branches, accounts began to grow,
Logs now whisper versions, checks hop into place,
Events spread the good news across the message space,
Hooray — schema inserted, the ledger wears a new coat!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.47% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description check ❓ Inconclusive The description 'Integrate in logs. Integrate in events.' is vague and lacks specific details about what is being integrated or how schemas are being integrated into logs and events. Provide more detailed description explaining which schema-related features are being integrated into logs and events, and how they enhance the system.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: introduce schemas' is concise and clearly indicates the main objective of adding schema functionality to the ledger system, aligning with the extensive schema-related changes throughout the codebase.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/schema

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
docs/api/README.md (1)

3792-3796: Document the new UPDATED_SCHEMA log type.

The enum for V2Log.type still lists only the four pre-existing values. With this PR we emit UPDATED_SCHEMA, so the docs should list it too to keep the API specification accurate.

🧹 Nitpick comments (2)
internal/schema.go (1)

19-24: Consider adding input validation and documenting CreatedAt handling.

The constructor has a few considerations:

  1. CreatedAt is not initialized: Given the bun:"created_at,nullzero" tag, this appears intentional (likely DB-managed via default value or trigger). Consider adding a comment documenting this behavior.

  2. Version validation: The constructor accepts any string for version, including empty strings. If version format matters (e.g., semver), consider adding validation.

Apply this diff to add validation and documentation:

+// NewSchema creates a new Schema instance with the given version and data.
+// CreatedAt will be set by the database on insert.
 func NewSchema(version string, data SchemaData) Schema {
+	if version == "" {
+		panic("version cannot be empty")
+	}
 	return Schema{
 		Version:    version,
 		SchemaData: data,
 	}
 }

Alternatively, return an error instead of panicking for better error handling.

internal/controller/ledger/controller_with_traces.go (1)

468-489: Trace span name typo

TraceWithMetric records this span under "UpdatedSchema", while the method (and metric) are named UpdateSchema. This makes the trace harder to locate and breaks naming conventions used elsewhere in this type. Please rename the operation string to "UpdateSchema" so traces stay consistent.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 58a84d9 and bd531c9.

⛔ Files ignored due to path filters (7)
  • go.mod is excluded by !**/*.mod
  • internal/storage/bucket/migrations/41-add-schema/notes.yaml is excluded by !**/*.yaml
  • openapi.yaml is excluded by !**/*.yaml
  • openapi/v2.yaml is excluded by !**/*.yaml
  • pkg/client/.speakeasy/gen.lock is excluded by !**/*.lock, !**/*.lock
  • pkg/client/.speakeasy/logs/naming.log is excluded by !**/*.log, !**/*.log
  • pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log is excluded by !**/*.log, !**/*.log
📒 Files selected for processing (43)
  • docs/api/README.md (2 hunks)
  • internal/README.md (35 hunks)
  • internal/api/bulking/mocks_ledger_controller_test.go (2 hunks)
  • internal/api/common/mocks_ledger_controller_test.go (2 hunks)
  • internal/api/v1/mocks_ledger_controller_test.go (2 hunks)
  • internal/api/v2/controllers_schema_insert.go (1 hunks)
  • internal/api/v2/controllers_schema_read.go (1 hunks)
  • internal/api/v2/mocks_ledger_controller_test.go (2 hunks)
  • internal/api/v2/routes.go (2 hunks)
  • internal/bus/listener.go (1 hunks)
  • internal/controller/ledger/controller.go (3 hunks)
  • internal/controller/ledger/controller_default.go (4 hunks)
  • internal/controller/ledger/controller_generated_test.go (2 hunks)
  • internal/controller/ledger/controller_with_events.go (1 hunks)
  • internal/controller/ledger/controller_with_too_many_client_handling.go (2 hunks)
  • internal/controller/ledger/controller_with_traces.go (4 hunks)
  • internal/controller/ledger/listener.go (1 hunks)
  • internal/controller/ledger/listener_generated_test.go (1 hunks)
  • internal/controller/ledger/store.go (2 hunks)
  • internal/controller/ledger/store_generated_test.go (2 hunks)
  • internal/log.go (5 hunks)
  • internal/schema.go (1 hunks)
  • internal/storage/bucket/migrations/41-add-schema/up.sql (1 hunks)
  • internal/storage/ledger/schema.go (1 hunks)
  • internal/storage/ledger/schema_test.go (1 hunks)
  • pkg/client/README.md (1 hunks)
  • pkg/client/docs/models/components/v2schema.md (1 hunks)
  • pkg/client/docs/models/components/v2schemadata.md (1 hunks)
  • pkg/client/docs/models/components/v2schemaresponse.md (1 hunks)
  • pkg/client/docs/models/operations/v2getschemarequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2getschemaresponse.md (1 hunks)
  • pkg/client/docs/models/operations/v2insertschemarequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2insertschemaresponse.md (1 hunks)
  • pkg/client/docs/sdks/v2/README.md (2 hunks)
  • pkg/client/models/components/v2schema.go (1 hunks)
  • pkg/client/models/components/v2schemadata.go (1 hunks)
  • pkg/client/models/components/v2schemaresponse.go (1 hunks)
  • pkg/client/models/operations/v2getschema.go (1 hunks)
  • pkg/client/models/operations/v2insertschema.go (1 hunks)
  • pkg/client/v2.go (1 hunks)
  • pkg/events/events.go (1 hunks)
  • pkg/events/message.go (1 hunks)
  • test/e2e/api_schema_test.go (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (28)
internal/controller/ledger/listener.go (3)
internal/log.go (1)
  • UpdatedSchema (373-375)
pkg/events/message.go (1)
  • UpdatedSchema (76-79)
internal/schema.go (1)
  • Schema (11-17)
pkg/client/models/components/v2schema.go (2)
pkg/client/models/components/v2schemadata.go (1)
  • V2SchemaData (6-7)
pkg/client/internal/utils/json.go (2)
  • MarshalJSON (21-115)
  • UnmarshalJSON (117-251)
internal/storage/ledger/schema_test.go (1)
internal/schema.go (2)
  • NewSchema (19-24)
  • SchemaData (8-9)
test/e2e/api_schema_test.go (5)
test/e2e/suite_test.go (1)
  • UseTemplatedDatabase (143-145)
pkg/client/models/operations/v2createledger.go (1)
  • V2CreateLedgerRequest (9-13)
pkg/client/models/operations/v2insertschema.go (1)
  • V2InsertSchemaRequest (9-15)
pkg/client/models/operations/v2getschema.go (1)
  • V2GetSchemaRequest (9-14)
pkg/client/models/components/v2schemaresponse.go (1)
  • V2SchemaResponse (5-8)
internal/storage/ledger/schema.go (2)
internal/controller/ledger/store.go (1)
  • Store (28-64)
internal/schema.go (1)
  • Schema (11-17)
internal/log.go (2)
internal/schema.go (1)
  • Schema (11-17)
pkg/events/message.go (1)
  • UpdatedSchema (76-79)
pkg/events/message.go (3)
internal/schema.go (1)
  • Schema (11-17)
internal/log.go (1)
  • UpdatedSchema (373-375)
pkg/events/events.go (3)
  • EventApp (5-5)
  • EventVersion (4-4)
  • EventTypeUpdatedSchema (11-11)
internal/controller/ledger/controller_with_too_many_client_handling.go (4)
internal/controller/ledger/controller.go (2)
  • UpdateSchema (132-135)
  • Controller (20-86)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
internal/schema.go (1)
  • Schema (11-17)
pkg/client/models/operations/v2getschema.go (1)
pkg/client/models/components/v2schemaresponse.go (1)
  • V2SchemaResponse (5-8)
internal/bus/listener.go (3)
pkg/events/message.go (2)
  • UpdatedSchema (76-79)
  • NewEventUpdatedSchema (81-89)
internal/schema.go (1)
  • Schema (11-17)
pkg/events/events.go (1)
  • EventTypeUpdatedSchema (11-11)
internal/controller/ledger/store_generated_test.go (1)
internal/schema.go (1)
  • Schema (11-17)
internal/controller/ledger/controller_with_traces.go (5)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (132-135)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (1)
  • UpdatedSchema (373-375)
internal/tracing/tracing.go (1)
  • TraceWithMetric (100-128)
internal/schema.go (1)
  • Schema (11-17)
pkg/client/v2.go (12)
pkg/client/models/operations/v2insertschema.go (2)
  • V2InsertSchemaRequest (9-15)
  • V2InsertSchemaResponse (38-40)
pkg/client/models/operations/options.go (4)
  • Option (41-41)
  • Options (32-39)
  • SupportedOptionRetries (15-15)
  • SupportedOptionTimeout (16-16)
pkg/client/internal/utils/utils.go (4)
  • ReplaceParameters (51-60)
  • MatchStatusCodes (71-92)
  • ConsumeRawBody (350-360)
  • UnmarshalJsonFromResponseBody (39-49)
pkg/client/internal/utils/pathparams.go (1)
  • GenerateURL (19-38)
pkg/client/internal/hooks/hooks.go (4)
  • HookContext (27-35)
  • BeforeRequestContext (37-39)
  • AfterErrorContext (45-47)
  • AfterSuccessContext (41-43)
pkg/client/internal/config/sdkconfiguration.go (1)
  • SDKConfiguration (16-25)
pkg/client/internal/utils/requestbody.go (1)
  • SerializeRequestBody (31-42)
pkg/client/internal/utils/security.go (1)
  • PopulateSecurity (27-92)
pkg/client/internal/utils/retries.go (3)
  • RetryConfig (23-23)
  • Retries (25-28)
  • Retry (30-104)
pkg/client/models/components/v2errorresponse.go (1)
  • V2ErrorResponse (5-9)
pkg/client/models/operations/v2getschema.go (2)
  • V2GetSchemaRequest (9-14)
  • V2GetSchemaResponse (30-34)
pkg/client/models/components/v2schemaresponse.go (1)
  • V2SchemaResponse (5-8)
internal/controller/ledger/store.go (1)
internal/schema.go (1)
  • Schema (11-17)
internal/controller/ledger/controller.go (3)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
internal/schema.go (2)
  • Schema (11-17)
  • SchemaData (8-9)
internal/controller/ledger/controller_default.go (5)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (132-135)
internal/log.go (2)
  • UpdatedSchema (373-375)
  • Log (89-101)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/controller/ledger/store.go (1)
  • Store (28-64)
internal/schema.go (2)
  • NewSchema (19-24)
  • Schema (11-17)
pkg/client/models/operations/v2insertschema.go (1)
pkg/client/models/components/v2schemadata.go (1)
  • V2SchemaData (6-7)
internal/controller/ledger/controller_generated_test.go (4)
internal/schema.go (1)
  • Schema (11-17)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (132-135)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
internal/controller/ledger/listener_generated_test.go (3)
internal/log.go (1)
  • UpdatedSchema (373-375)
pkg/events/message.go (1)
  • UpdatedSchema (76-79)
internal/schema.go (1)
  • Schema (11-17)
pkg/client/models/components/v2schemaresponse.go (1)
pkg/client/models/components/v2schema.go (1)
  • V2Schema (11-18)
internal/api/common/mocks_ledger_controller_test.go (3)
internal/schema.go (1)
  • Schema (11-17)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
internal/api/v1/mocks_ledger_controller_test.go (4)
internal/schema.go (1)
  • Schema (11-17)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (132-135)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
internal/schema.go (1)
cmd/root.go (1)
  • Version (23-23)
internal/controller/ledger/controller_with_events.go (5)
internal/controller/ledger/controller.go (2)
  • UpdateSchema (132-135)
  • Controller (20-86)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
pkg/events/message.go (1)
  • UpdatedSchema (76-79)
internal/schema.go (1)
  • Schema (11-17)
internal/api/bulking/mocks_ledger_controller_test.go (4)
internal/schema.go (1)
  • Schema (11-17)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (132-135)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
internal/api/v2/controllers_schema_read.go (1)
internal/api/common/errors.go (1)
  • HandleCommonErrors (35-42)
internal/api/v2/mocks_ledger_controller_test.go (5)
internal/api/common/mocks_ledger_controller_test.go (2)
  • LedgerController (26-30)
  • LedgerControllerMockRecorder (33-35)
internal/schema.go (1)
  • Schema (11-17)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (132-135)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
internal/api/v2/controllers_schema_insert.go (3)
internal/schema.go (1)
  • SchemaData (8-9)
internal/api/common/errors.go (1)
  • HandleCommonWriteErrors (44-55)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (132-135)
🪛 markdownlint-cli2 (0.18.1)
pkg/client/docs/sdks/v2/README.md

246-246: Hard tabs
Column: 1

(MD010, no-hard-tabs)


247-247: Hard tabs
Column: 1

(MD010, no-hard-tabs)


248-248: Hard tabs
Column: 1

(MD010, no-hard-tabs)


249-249: Hard tabs
Column: 1

(MD010, no-hard-tabs)


250-250: Hard tabs
Column: 1

(MD010, no-hard-tabs)


251-251: Hard tabs
Column: 1

(MD010, no-hard-tabs)


307-307: Hard tabs
Column: 1

(MD010, no-hard-tabs)


308-308: Hard tabs
Column: 1

(MD010, no-hard-tabs)


309-309: Hard tabs
Column: 1

(MD010, no-hard-tabs)


310-310: Hard tabs
Column: 1

(MD010, no-hard-tabs)


311-311: Hard tabs
Column: 1

(MD010, no-hard-tabs)


312-312: Hard tabs
Column: 1

(MD010, no-hard-tabs)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Tests
  • GitHub Check: Dirty
🔇 Additional comments (23)
pkg/client/README.md (1)

133-134: LGTM!

The documentation additions for the new schema operations are clear and follow the established pattern for documenting V2 ledger operations.

internal/schema.go (1)

8-9: Verify if SchemaData is intentionally empty.

The SchemaData struct is currently empty, which might be a placeholder for future schema content. If this is intentional for extensibility, consider adding a comment explaining this design choice.

Do you want to add schema content fields to SchemaData now, or is this structure reserved for future use?

internal/README.md (1)

1-1218: LGTM!

This is auto-generated documentation (via gomarkdoc) that correctly reflects the new Schema-related types introduced in the codebase. The documentation is comprehensive and follows the established format.

pkg/events/events.go (1)

10-11: LGTM!

The addition of EventTypeUpdatedSchema constant follows the established naming pattern and integrates cleanly with the existing event types.

pkg/client/docs/models/operations/v2getschemarequest.md (1)

1-9: LGTM!

The documentation for V2GetSchemaRequest is clear, complete, and includes helpful examples for both required fields.

pkg/client/docs/models/components/v2schemaresponse.md (1)

1-8: LGTM!

The documentation for V2SchemaResponse is clear and correctly references the V2Schema component documentation.

pkg/client/docs/models/components/v2schema.md (1)

1-12: LGTM!

The documentation for V2Schema is comprehensive and provides clear descriptions and examples for all three required fields. The structure aligns well with the underlying Schema type in the codebase.

internal/api/v2/routes.go (1)

73-74: LGTM!

The new schema routes follow RESTful conventions and are properly placed within the ledger middleware group for authorization and tracing.

internal/bus/listener.go (1)

28-34: LGTM!

The UpdatedSchema implementation correctly follows the established pattern of other listener methods, properly constructing and publishing the schema update event.

internal/storage/ledger/schema_test.go (1)

14-28: LGTM!

The test correctly verifies schema insertion with appropriate assertions. The use of t.Parallel() and proper error checking follows good testing practices.

pkg/client/models/components/v2schemadata.go (1)

5-7: Empty struct is expected in generated code. V2SchemaData is autogenerated by Speakeasy from the OpenAPI spec, which currently defines no properties for the data object; no changes required.

pkg/client/models/components/v2schema.go (1)

1-50: LGTM!

The generated V2Schema model follows standard patterns with proper JSON marshaling, nil-safe getters, and correct field annotations.

pkg/client/docs/models/operations/v2insertschemarequest.md (1)

1-10: LGTM!

The documentation clearly describes the V2InsertSchemaRequest model with appropriate field descriptions and examples.

internal/controller/ledger/controller_with_events.go (1)

154-166: LGTM!

The UpdateSchema method correctly follows the established pattern for event-emitting methods in this controller, delegating to the underlying controller and emitting events only when not in DryRun mode.

internal/api/v2/controllers_schema_read.go (1)

13-26: LGTM!

The readSchema handler correctly extracts the version parameter, fetches the schema, and handles errors appropriately with proper 404 responses for not found cases.

internal/controller/ledger/store.go (1)

48-49: LGTM!

The InsertSchema and FindSchema methods properly extend the Store interface to support schema persistence and retrieval functionality.

test/e2e/api_schema_test.go (1)

1-108: LGTM!

The E2E test suite provides comprehensive coverage of the schema API, including insertion, retrieval, multiple versions, error cases for non-existent schemas, and duplicate version handling.

pkg/client/models/components/v2schemaresponse.go (1)

1-15: LGTM!

The V2SchemaResponse wrapper follows standard patterns with a nil-safe getter method.

internal/api/v2/mocks_ledger_controller_test.go (1)

214-227: LGTM! Auto-generated mocks are correctly structured.

The GetSchema and UpdateSchema mock methods follow the standard gomock pattern and have signatures matching the Controller interface.

Also applies to: 439-453

internal/api/v1/mocks_ledger_controller_test.go (1)

214-227: LGTM! Auto-generated mocks are correctly structured.

The GetSchema and UpdateSchema mock methods follow the standard gomock pattern and have signatures matching the Controller interface.

Also applies to: 439-453

internal/controller/ledger/controller_with_too_many_client_handling.go (1)

123-148: LGTM! Retry wrappers follow established pattern.

Both UpdateSchema and GetSchema correctly wrap the underlying controller methods with the handleRetry mechanism, maintaining consistency with other wrapped methods in this file.

internal/controller/ledger/store_generated_test.go (1)

151-164: LGTM! Auto-generated store mocks are correctly structured.

The FindSchema and InsertSchema mock methods follow the standard gomock pattern for the Store interface.

Also applies to: 210-222

internal/api/bulking/mocks_ledger_controller_test.go (1)

214-227: LGTM! Auto-generated mocks are correctly structured.

The GetSchema and UpdateSchema mock methods follow the standard gomock pattern and have signatures matching the Controller interface.

Also applies to: 439-453

@codecov
Copy link

codecov bot commented Oct 5, 2025

Codecov Report

❌ Patch coverage is 80.70175% with 143 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.08%. Comparing base (4835b76) to head (9838c3a).

Files with missing lines Patch % Lines
internal/chart.go 89.41% 10 Missing and 10 partials ⚠️
internal/controller/ledger/controller_default.go 62.50% 6 Missing and 12 partials ⚠️
internal/controller/ledger/log_process.go 58.13% 10 Missing and 8 partials ⚠️
internal/log.go 56.41% 11 Missing and 6 partials ⚠️
...ternal/controller/ledger/controller_with_traces.go 79.36% 6 Missing and 7 partials ⚠️
internal/storage/ledger/resource_schemas.go 56.52% 9 Missing and 1 partial ⚠️
internal/api/common/errors.go 20.00% 4 Missing and 4 partials ⚠️
...ledger/controller_with_too_many_client_handling.go 78.12% 0 Missing and 7 partials ⚠️
internal/errors.go 72.22% 5 Missing ⚠️
internal/api/bulking/bulker.go 72.72% 0 Missing and 3 partials ⚠️
... and 12 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1064      +/-   ##
==========================================
- Coverage   80.45%   77.08%   -3.37%     
==========================================
  Files         190      197       +7     
  Lines        9454    10240     +786     
==========================================
+ Hits         7606     7894     +288     
- Misses       1257     1420     +163     
- Partials      591      926     +335     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
pkg/events/message.go (1)

78-79: Align JSON payload field with log/event contract.

The UpdatedSchema payload must serialize the schema under "data" (see internal/log.go:372-374). Keeping "schema" breaks consumers expecting the documented field name.

 type UpdatedSchema struct {
 	Ledger string        `json:"ledger"`
-	Schema ledger.Schema `json:"schema"`
+	Schema ledger.Schema `json:"data"`
 }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bd531c9 and 3d71d9d.

📒 Files selected for processing (4)
  • internal/api/v2/controllers_schema_read.go (1 hunks)
  • internal/controller/ledger/listener.go (1 hunks)
  • internal/storage/bucket/migrations/41-add-schema/up.sql (1 hunks)
  • pkg/events/message.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • internal/api/v2/controllers_schema_read.go
  • internal/storage/bucket/migrations/41-add-schema/up.sql
  • internal/controller/ledger/listener.go
🧰 Additional context used
🧬 Code graph analysis (1)
pkg/events/message.go (3)
internal/schema.go (1)
  • Schema (11-17)
internal/log.go (1)
  • UpdatedSchema (373-375)
pkg/events/events.go (3)
  • EventApp (5-5)
  • EventVersion (4-4)
  • EventTypeUpdatedSchema (11-11)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Tests
  • GitHub Check: Dirty

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
pkg/client/docs/sdks/v2/README.md (1)

246-251: Replace hard tabs with spaces.

The static analysis tool flagged hard tabs in the code examples. Replace them with spaces to comply with Markdown formatting standards.

Example fix for lines 246-251:

 package main
 
 import(
-	"context"
-	"os"
-	"github.com/formancehq/ledger/pkg/client/models/components"
-	"github.com/formancehq/ledger/pkg/client"
-	"github.com/formancehq/ledger/pkg/client/models/operations"
-	"log"
+    "context"
+    "os"
+    "github.com/formancehq/ledger/pkg/client/models/components"
+    "github.com/formancehq/ledger/pkg/client"
+    "github.com/formancehq/ledger/pkg/client/models/operations"
+    "log"
 )

Apply similar changes to lines 307-312.

Also applies to: 307-312

internal/controller/ledger/controller_with_traces.go (1)

46-48: Rename tracing metric to match schema terminology

The new instrumentation still uses the old “chart of account” name and the span operation is titled UpdatedSchema, which deviates from the rest of the controller methods. Renaming the histogram/op keeps metrics, traces, and local variables aligned with the new schema concept and avoids shipping yet another metric name our observability stack must support.

Apply this diff:

--- a/internal/controller/ledger/controller_with_traces.go
+++ b/internal/controller/ledger/controller_with_traces.go
@@
-	updateChartOfAccountHistogram      metric.Int64Histogram
+	updateSchemaHistogram              metric.Int64Histogram
@@
-	ret.updateChartOfAccountHistogram, err = meter.Int64Histogram("controller.update_chart_of_account")
+	ret.updateSchemaHistogram, err = meter.Int64Histogram("controller.update_schema")
@@
-func (c *ControllerWithTraces) UpdateSchema(ctx context.Context, parameters Parameters[UpdateSchema]) (*ledger.Log, *ledger.UpdatedSchema, error) {
+func (c *ControllerWithTraces) UpdateSchema(ctx context.Context, parameters Parameters[UpdateSchema]) (*ledger.Log, *ledger.UpdatedSchema, error) {
 	var (
-		updatedChartOfAccount *ledger.UpdatedSchema
+		updatedSchema *ledger.UpdatedSchema
 		log                   *ledger.Log
 		err                   error
 	)
 	_, err = tracing.TraceWithMetric(
 		ctx,
-		"UpdatedSchema",
+		"UpdateSchema",
 		c.tracer,
-		c.updateChartOfAccountHistogram,
+		c.updateSchemaHistogram,
 		func(ctx context.Context) (any, error) {
-			log, updatedChartOfAccount, err = c.underlying.UpdateSchema(ctx, parameters)
+			log, updatedSchema, err = c.underlying.UpdateSchema(ctx, parameters)
 			return nil, err
 		},
 	)
 	if err != nil {
 		return nil, nil, err
 	}
 
-	return log, updatedChartOfAccount, nil
+	return log, updatedSchema, nil
 }

Also applies to: 153-160, 468-489

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3d71d9d and 5d4e210.

⛔ Files ignored due to path filters (6)
  • internal/storage/bucket/migrations/41-add-schema/notes.yaml is excluded by !**/*.yaml
  • openapi.yaml is excluded by !**/*.yaml
  • openapi/v2.yaml is excluded by !**/*.yaml
  • pkg/client/.speakeasy/gen.lock is excluded by !**/*.lock, !**/*.lock
  • pkg/client/.speakeasy/logs/naming.log is excluded by !**/*.log, !**/*.log
  • pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log is excluded by !**/*.log, !**/*.log
📒 Files selected for processing (43)
  • docs/api/README.md (2 hunks)
  • internal/README.md (35 hunks)
  • internal/api/bulking/mocks_ledger_controller_test.go (2 hunks)
  • internal/api/common/mocks_ledger_controller_test.go (2 hunks)
  • internal/api/v1/mocks_ledger_controller_test.go (2 hunks)
  • internal/api/v2/controllers_schema_insert.go (1 hunks)
  • internal/api/v2/controllers_schema_read.go (1 hunks)
  • internal/api/v2/mocks_ledger_controller_test.go (2 hunks)
  • internal/api/v2/routes.go (2 hunks)
  • internal/bus/listener.go (1 hunks)
  • internal/controller/ledger/controller.go (3 hunks)
  • internal/controller/ledger/controller_default.go (4 hunks)
  • internal/controller/ledger/controller_generated_test.go (2 hunks)
  • internal/controller/ledger/controller_with_events.go (1 hunks)
  • internal/controller/ledger/controller_with_too_many_client_handling.go (2 hunks)
  • internal/controller/ledger/controller_with_traces.go (4 hunks)
  • internal/controller/ledger/listener.go (1 hunks)
  • internal/controller/ledger/listener_generated_test.go (1 hunks)
  • internal/controller/ledger/store.go (2 hunks)
  • internal/controller/ledger/store_generated_test.go (2 hunks)
  • internal/log.go (5 hunks)
  • internal/schema.go (1 hunks)
  • internal/storage/bucket/migrations/41-add-schema/up.sql (1 hunks)
  • internal/storage/ledger/schema.go (1 hunks)
  • internal/storage/ledger/schema_test.go (1 hunks)
  • pkg/client/README.md (1 hunks)
  • pkg/client/docs/models/components/v2schema.md (1 hunks)
  • pkg/client/docs/models/components/v2schemadata.md (1 hunks)
  • pkg/client/docs/models/components/v2schemaresponse.md (1 hunks)
  • pkg/client/docs/models/operations/v2getschemarequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2getschemaresponse.md (1 hunks)
  • pkg/client/docs/models/operations/v2insertschemarequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2insertschemaresponse.md (1 hunks)
  • pkg/client/docs/sdks/v2/README.md (2 hunks)
  • pkg/client/models/components/v2schema.go (1 hunks)
  • pkg/client/models/components/v2schemadata.go (1 hunks)
  • pkg/client/models/components/v2schemaresponse.go (1 hunks)
  • pkg/client/models/operations/v2getschema.go (1 hunks)
  • pkg/client/models/operations/v2insertschema.go (1 hunks)
  • pkg/client/v2.go (1 hunks)
  • pkg/events/events.go (1 hunks)
  • pkg/events/message.go (1 hunks)
  • test/e2e/api_schema_test.go (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • test/e2e/api_schema_test.go
  • pkg/client/docs/models/components/v2schema.md
🚧 Files skipped from review as they are similar to previous changes (13)
  • pkg/client/docs/models/operations/v2insertschemaresponse.md
  • pkg/client/models/components/v2schemaresponse.go
  • pkg/client/models/operations/v2insertschema.go
  • pkg/client/docs/models/operations/v2insertschemarequest.md
  • internal/schema.go
  • pkg/client/docs/models/components/v2schemadata.md
  • internal/storage/ledger/schema.go
  • internal/api/bulking/mocks_ledger_controller_test.go
  • internal/storage/bucket/migrations/41-add-schema/up.sql
  • pkg/client/docs/models/operations/v2getschemarequest.md
  • pkg/client/models/operations/v2getschema.go
  • internal/controller/ledger/store_generated_test.go
  • pkg/client/docs/models/components/v2schemaresponse.md
🧰 Additional context used
🧬 Code graph analysis (20)
internal/bus/listener.go (3)
pkg/events/message.go (2)
  • UpdatedSchema (76-79)
  • NewEventUpdatedSchema (81-89)
internal/schema.go (1)
  • Schema (11-17)
pkg/events/events.go (1)
  • EventTypeUpdatedSchema (11-11)
internal/controller/ledger/controller_with_events.go (5)
internal/controller/ledger/controller.go (2)
  • UpdateSchema (132-135)
  • Controller (20-86)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
pkg/events/message.go (1)
  • UpdatedSchema (76-79)
internal/schema.go (1)
  • Schema (11-17)
pkg/events/message.go (3)
internal/schema.go (1)
  • Schema (11-17)
internal/log.go (1)
  • UpdatedSchema (373-375)
pkg/events/events.go (3)
  • EventApp (5-5)
  • EventVersion (4-4)
  • EventTypeUpdatedSchema (11-11)
internal/controller/ledger/store.go (1)
internal/schema.go (1)
  • Schema (11-17)
internal/api/v1/mocks_ledger_controller_test.go (4)
internal/schema.go (1)
  • Schema (11-17)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (132-135)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
internal/controller/ledger/controller_default.go (5)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (132-135)
internal/log.go (2)
  • UpdatedSchema (373-375)
  • Log (89-101)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/controller/ledger/store.go (1)
  • Store (28-64)
internal/schema.go (2)
  • NewSchema (19-24)
  • Schema (11-17)
internal/controller/ledger/listener.go (3)
internal/log.go (1)
  • UpdatedSchema (373-375)
pkg/events/message.go (1)
  • UpdatedSchema (76-79)
internal/schema.go (1)
  • Schema (11-17)
internal/api/v2/controllers_schema_insert.go (3)
internal/schema.go (1)
  • SchemaData (8-9)
internal/api/common/errors.go (1)
  • HandleCommonWriteErrors (44-55)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (132-135)
internal/api/common/mocks_ledger_controller_test.go (7)
internal/api/bulking/mocks_ledger_controller_test.go (2)
  • LedgerController (26-30)
  • LedgerControllerMockRecorder (33-35)
internal/api/v1/mocks_ledger_controller_test.go (2)
  • LedgerController (26-30)
  • LedgerControllerMockRecorder (33-35)
internal/api/v2/mocks_ledger_controller_test.go (2)
  • LedgerController (26-30)
  • LedgerControllerMockRecorder (33-35)
internal/schema.go (1)
  • Schema (11-17)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (132-135)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
internal/controller/ledger/controller_generated_test.go (4)
internal/schema.go (1)
  • Schema (11-17)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (132-135)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
internal/controller/ledger/controller.go (4)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (1)
  • UpdatedSchema (373-375)
pkg/events/message.go (1)
  • UpdatedSchema (76-79)
internal/schema.go (2)
  • Schema (11-17)
  • SchemaData (8-9)
internal/controller/ledger/listener_generated_test.go (3)
internal/log.go (1)
  • UpdatedSchema (373-375)
pkg/events/message.go (1)
  • UpdatedSchema (76-79)
internal/schema.go (1)
  • Schema (11-17)
pkg/client/models/components/v2schema.go (2)
pkg/client/models/components/v2schemadata.go (1)
  • V2SchemaData (6-7)
pkg/client/internal/utils/json.go (2)
  • MarshalJSON (21-115)
  • UnmarshalJSON (117-251)
internal/controller/ledger/controller_with_traces.go (5)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (132-135)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (1)
  • UpdatedSchema (373-375)
internal/tracing/tracing.go (1)
  • TraceWithMetric (100-128)
internal/schema.go (1)
  • Schema (11-17)
internal/log.go (2)
internal/schema.go (1)
  • Schema (11-17)
pkg/events/message.go (1)
  • UpdatedSchema (76-79)
pkg/client/v2.go (11)
pkg/client/models/operations/v2insertschema.go (2)
  • V2InsertSchemaRequest (9-15)
  • V2InsertSchemaResponse (38-40)
pkg/client/models/operations/options.go (4)
  • Option (41-41)
  • Options (32-39)
  • SupportedOptionRetries (15-15)
  • SupportedOptionTimeout (16-16)
pkg/client/internal/utils/utils.go (3)
  • ReplaceParameters (51-60)
  • ConsumeRawBody (350-360)
  • UnmarshalJsonFromResponseBody (39-49)
pkg/client/internal/utils/pathparams.go (1)
  • GenerateURL (19-38)
pkg/client/internal/hooks/hooks.go (4)
  • HookContext (27-35)
  • BeforeRequestContext (37-39)
  • AfterErrorContext (45-47)
  • AfterSuccessContext (41-43)
pkg/client/internal/config/sdkconfiguration.go (1)
  • SDKConfiguration (16-25)
pkg/client/internal/utils/requestbody.go (1)
  • SerializeRequestBody (31-42)
pkg/client/internal/utils/security.go (1)
  • PopulateSecurity (27-92)
pkg/client/internal/utils/retries.go (3)
  • RetryConfig (23-23)
  • Retries (25-28)
  • Retry (30-104)
pkg/client/models/operations/v2getschema.go (2)
  • V2GetSchemaRequest (9-14)
  • V2GetSchemaResponse (30-34)
pkg/client/models/components/v2schemaresponse.go (1)
  • V2SchemaResponse (5-8)
internal/controller/ledger/controller_with_too_many_client_handling.go (4)
internal/controller/ledger/controller.go (2)
  • UpdateSchema (132-135)
  • Controller (20-86)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
internal/schema.go (1)
  • Schema (11-17)
internal/api/v2/mocks_ledger_controller_test.go (4)
internal/schema.go (1)
  • Schema (11-17)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (132-135)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
internal/storage/ledger/schema_test.go (1)
internal/schema.go (2)
  • NewSchema (19-24)
  • SchemaData (8-9)
internal/api/v2/controllers_schema_read.go (1)
internal/api/common/errors.go (1)
  • HandleCommonErrors (35-42)
🪛 markdownlint-cli2 (0.18.1)
pkg/client/docs/sdks/v2/README.md

246-246: Hard tabs
Column: 1

(MD010, no-hard-tabs)


247-247: Hard tabs
Column: 1

(MD010, no-hard-tabs)


248-248: Hard tabs
Column: 1

(MD010, no-hard-tabs)


249-249: Hard tabs
Column: 1

(MD010, no-hard-tabs)


250-250: Hard tabs
Column: 1

(MD010, no-hard-tabs)


251-251: Hard tabs
Column: 1

(MD010, no-hard-tabs)


307-307: Hard tabs
Column: 1

(MD010, no-hard-tabs)


308-308: Hard tabs
Column: 1

(MD010, no-hard-tabs)


309-309: Hard tabs
Column: 1

(MD010, no-hard-tabs)


310-310: Hard tabs
Column: 1

(MD010, no-hard-tabs)


311-311: Hard tabs
Column: 1

(MD010, no-hard-tabs)


312-312: Hard tabs
Column: 1

(MD010, no-hard-tabs)

🔇 Additional comments (10)
internal/controller/ledger/controller_default.go (4)

60-61: LGTM!

The updateSchemaLp field is correctly added to the DefaultController struct and properly initialized in NewDefaultController (line 159), following the same pattern as other log processors.


63-80: LGTM!

The implementation of UpdateSchema, updateSchema, and GetSchema methods is correct:

  • UpdateSchema properly delegates to the log processor following the established pattern
  • updateSchema creates a schema using ledger.NewSchema and persists it via store.InsertSchema
  • GetSchema provides a straightforward read-through to the store
  • Error handling is properly propagated

The pattern is consistent with other operations like CreateTransaction and RevertTransaction.


274-277: LGTM!

The UpdatedSchema case in importLog correctly handles schema import by inserting the schema into the store. The implementation is consistent with other log payload types and includes proper error handling with context.


159-159: LGTM!

The updateSchemaLp log processor is properly initialized with the correct type parameters and follows the same pattern as other log processors in the constructor.

internal/controller/ledger/store.go (1)

48-49: Implementations exist for new Store interface methods
The generated MockStore (internal/controller/ledger/store_generated_test.go) and the concrete Store in internal/storage/ledger/schema.go both define InsertSchema and FindSchema.

docs/api/README.md (2)

313-415: Docs for schema endpoints look solid.

The new insert/get schema sections align with the API surface (routes, controllers, client models) and capture the right parameters, responses, and auth hints. Nicely integrated.


5340-5407: Schema model definitions are consistent.

V2SchemaData, V2Schema, and V2SchemaResponse mirror the new public types and keep the anchors accurate for cross-references—looks good.

pkg/client/README.md (1)

133-134: SDK operations list updated correctly.

The Ledger.V2 section now advertises InsertSchema and GetSchema, matching the generated client surface and docs references.

internal/api/v2/mocks_ledger_controller_test.go (2)

214-227: LGTM! Generated mock follows gomock patterns correctly.

The GetSchema mock method and its recorder are properly generated and follow the same pattern as other controller mock methods in this file.


439-453: LGTM! Generated mock follows gomock patterns correctly.

The UpdateSchema mock method and its recorder are properly generated and consistent with other three-return-value controller methods like CreateTransaction and RevertTransaction.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (1)
pkg/client/models/components/v2schemadata.go (1)

5-7: Confirm the empty struct limitation is tracked for the next PR.

As noted in previous review, V2SchemaData is currently an empty struct, which means:

  • JSON encoding always produces {}
  • No actual schema data can be supplied in InsertSchema/UpdateSchema operations

The author acknowledged this will be addressed in a future PR. Please ensure this is tracked (e.g., in a GitHub issue or the next PR's scope) so the schema payload can be properly populated.

Based on learnings from past review.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5d4e210 and 96ec894.

⛔ Files ignored due to path filters (6)
  • internal/storage/bucket/migrations/41-add-schema/notes.yaml is excluded by !**/*.yaml
  • openapi.yaml is excluded by !**/*.yaml
  • openapi/v2.yaml is excluded by !**/*.yaml
  • pkg/client/.speakeasy/gen.lock is excluded by !**/*.lock, !**/*.lock
  • pkg/client/.speakeasy/logs/naming.log is excluded by !**/*.log, !**/*.log
  • pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log is excluded by !**/*.log, !**/*.log
📒 Files selected for processing (43)
  • docs/api/README.md (2 hunks)
  • internal/README.md (35 hunks)
  • internal/api/bulking/mocks_ledger_controller_test.go (2 hunks)
  • internal/api/common/mocks_ledger_controller_test.go (2 hunks)
  • internal/api/v1/mocks_ledger_controller_test.go (2 hunks)
  • internal/api/v2/controllers_schema_insert.go (1 hunks)
  • internal/api/v2/controllers_schema_read.go (1 hunks)
  • internal/api/v2/mocks_ledger_controller_test.go (2 hunks)
  • internal/api/v2/routes.go (2 hunks)
  • internal/bus/listener.go (1 hunks)
  • internal/controller/ledger/controller.go (3 hunks)
  • internal/controller/ledger/controller_default.go (4 hunks)
  • internal/controller/ledger/controller_generated_test.go (2 hunks)
  • internal/controller/ledger/controller_with_events.go (1 hunks)
  • internal/controller/ledger/controller_with_too_many_client_handling.go (2 hunks)
  • internal/controller/ledger/controller_with_traces.go (4 hunks)
  • internal/controller/ledger/listener.go (1 hunks)
  • internal/controller/ledger/listener_generated_test.go (1 hunks)
  • internal/controller/ledger/store.go (2 hunks)
  • internal/controller/ledger/store_generated_test.go (2 hunks)
  • internal/log.go (5 hunks)
  • internal/schema.go (1 hunks)
  • internal/storage/bucket/migrations/41-add-schema/up.sql (1 hunks)
  • internal/storage/ledger/schema.go (1 hunks)
  • internal/storage/ledger/schema_test.go (1 hunks)
  • pkg/client/README.md (1 hunks)
  • pkg/client/docs/models/components/v2schema.md (1 hunks)
  • pkg/client/docs/models/components/v2schemadata.md (1 hunks)
  • pkg/client/docs/models/components/v2schemaresponse.md (1 hunks)
  • pkg/client/docs/models/operations/v2getschemarequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2getschemaresponse.md (1 hunks)
  • pkg/client/docs/models/operations/v2insertschemarequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2insertschemaresponse.md (1 hunks)
  • pkg/client/docs/sdks/v2/README.md (2 hunks)
  • pkg/client/models/components/v2schema.go (1 hunks)
  • pkg/client/models/components/v2schemadata.go (1 hunks)
  • pkg/client/models/components/v2schemaresponse.go (1 hunks)
  • pkg/client/models/operations/v2getschema.go (1 hunks)
  • pkg/client/models/operations/v2insertschema.go (1 hunks)
  • pkg/client/v2.go (1 hunks)
  • pkg/events/events.go (1 hunks)
  • pkg/events/message.go (1 hunks)
  • test/e2e/api_schema_test.go (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • pkg/client/docs/models/components/v2schemaresponse.md
🚧 Files skipped from review as they are similar to previous changes (22)
  • internal/controller/ledger/listener.go
  • internal/bus/listener.go
  • pkg/client/docs/models/operations/v2insertschemaresponse.md
  • internal/storage/bucket/migrations/41-add-schema/up.sql
  • pkg/client/docs/models/operations/v2getschemarequest.md
  • internal/controller/ledger/listener_generated_test.go
  • pkg/client/docs/models/components/v2schemadata.md
  • pkg/client/docs/models/components/v2schema.md
  • internal/api/v2/controllers_schema_read.go
  • pkg/client/docs/models/operations/v2insertschemarequest.md
  • internal/api/v2/controllers_schema_insert.go
  • internal/controller/ledger/controller_with_too_many_client_handling.go
  • internal/controller/ledger/controller_default.go
  • internal/api/v2/mocks_ledger_controller_test.go
  • pkg/client/models/components/v2schemaresponse.go
  • pkg/client/models/operations/v2getschema.go
  • pkg/client/models/operations/v2insertschema.go
  • internal/api/v1/mocks_ledger_controller_test.go
  • internal/storage/ledger/schema.go
  • internal/storage/ledger/schema_test.go
  • internal/controller/ledger/store_generated_test.go
  • pkg/events/events.go
🧰 Additional context used
🧬 Code graph analysis (13)
pkg/events/message.go (3)
internal/schema.go (1)
  • Schema (11-17)
internal/log.go (1)
  • UpdatedSchema (373-375)
pkg/events/events.go (3)
  • EventApp (5-5)
  • EventVersion (4-4)
  • EventTypeUpdatedSchema (11-11)
internal/schema.go (1)
cmd/root.go (1)
  • Version (23-23)
internal/controller/ledger/controller_with_traces.go (5)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (132-135)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (1)
  • UpdatedSchema (373-375)
internal/tracing/tracing.go (1)
  • TraceWithMetric (100-128)
internal/schema.go (1)
  • Schema (11-17)
internal/api/bulking/mocks_ledger_controller_test.go (6)
internal/api/common/mocks_ledger_controller_test.go (2)
  • LedgerController (26-30)
  • LedgerControllerMockRecorder (33-35)
internal/schema.go (1)
  • Schema (11-17)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (132-135)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
pkg/events/message.go (1)
  • UpdatedSchema (76-79)
internal/controller/ledger/controller_with_events.go (5)
internal/controller/ledger/controller.go (2)
  • UpdateSchema (132-135)
  • Controller (20-86)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
pkg/events/message.go (1)
  • UpdatedSchema (76-79)
internal/schema.go (1)
  • Schema (11-17)
test/e2e/api_schema_test.go (5)
test/e2e/suite_test.go (1)
  • UseTemplatedDatabase (143-145)
pkg/client/models/operations/v2createledger.go (1)
  • V2CreateLedgerRequest (9-13)
pkg/client/models/operations/v2insertschema.go (1)
  • V2InsertSchemaRequest (9-15)
pkg/client/models/operations/v2getschema.go (1)
  • V2GetSchemaRequest (9-14)
pkg/client/models/components/v2schemaresponse.go (1)
  • V2SchemaResponse (5-8)
internal/api/common/mocks_ledger_controller_test.go (6)
internal/api/bulking/mocks_ledger_controller_test.go (2)
  • LedgerController (26-30)
  • LedgerControllerMockRecorder (33-35)
internal/api/v1/mocks_ledger_controller_test.go (2)
  • LedgerController (26-30)
  • LedgerControllerMockRecorder (33-35)
internal/api/v2/mocks_ledger_controller_test.go (2)
  • LedgerController (26-30)
  • LedgerControllerMockRecorder (33-35)
internal/schema.go (1)
  • Schema (11-17)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
internal/log.go (2)
internal/schema.go (1)
  • Schema (11-17)
pkg/events/message.go (1)
  • UpdatedSchema (76-79)
pkg/client/models/components/v2schema.go (2)
pkg/client/models/components/v2schemadata.go (1)
  • V2SchemaData (6-7)
pkg/client/internal/utils/json.go (2)
  • MarshalJSON (21-115)
  • UnmarshalJSON (117-251)
pkg/client/v2.go (12)
pkg/client/models/operations/v2insertschema.go (2)
  • V2InsertSchemaRequest (9-15)
  • V2InsertSchemaResponse (38-40)
pkg/client/models/operations/options.go (4)
  • Option (41-41)
  • Options (32-39)
  • SupportedOptionRetries (15-15)
  • SupportedOptionTimeout (16-16)
pkg/client/internal/utils/utils.go (3)
  • ReplaceParameters (51-60)
  • ConsumeRawBody (350-360)
  • UnmarshalJsonFromResponseBody (39-49)
pkg/client/internal/utils/pathparams.go (1)
  • GenerateURL (19-38)
pkg/client/internal/hooks/hooks.go (4)
  • HookContext (27-35)
  • BeforeRequestContext (37-39)
  • AfterErrorContext (45-47)
  • AfterSuccessContext (41-43)
pkg/client/internal/config/sdkconfiguration.go (1)
  • SDKConfiguration (16-25)
pkg/client/internal/utils/requestbody.go (1)
  • SerializeRequestBody (31-42)
pkg/client/internal/utils/security.go (1)
  • PopulateSecurity (27-92)
pkg/client/internal/utils/retries.go (3)
  • RetryConfig (23-23)
  • Retries (25-28)
  • Retry (30-104)
pkg/client/models/components/v2errorresponse.go (1)
  • V2ErrorResponse (5-9)
pkg/client/models/operations/v2getschema.go (2)
  • V2GetSchemaRequest (9-14)
  • V2GetSchemaResponse (30-34)
pkg/client/models/components/v2schemaresponse.go (1)
  • V2SchemaResponse (5-8)
internal/controller/ledger/controller_generated_test.go (4)
internal/schema.go (1)
  • Schema (11-17)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (132-135)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
internal/controller/ledger/controller.go (4)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (1)
  • UpdatedSchema (373-375)
pkg/events/message.go (1)
  • UpdatedSchema (76-79)
internal/schema.go (2)
  • Schema (11-17)
  • SchemaData (8-9)
internal/controller/ledger/store.go (1)
internal/schema.go (1)
  • Schema (11-17)
🪛 markdownlint-cli2 (0.18.1)
docs/api/README.md

344-344: Blank line inside blockquote

(MD028, no-blanks-blockquote)


390-390: Blank line inside blockquote

(MD028, no-blanks-blockquote)


5354-5354: Multiple headings with the same content

(MD024, no-duplicate-heading)


5356-5356: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


5376-5376: Multiple headings with the same content

(MD024, no-duplicate-heading)


5402-5402: Multiple headings with the same content

(MD024, no-duplicate-heading)

pkg/client/docs/sdks/v2/README.md

246-246: Hard tabs
Column: 1

(MD010, no-hard-tabs)


247-247: Hard tabs
Column: 1

(MD010, no-hard-tabs)


248-248: Hard tabs
Column: 1

(MD010, no-hard-tabs)


249-249: Hard tabs
Column: 1

(MD010, no-hard-tabs)


250-250: Hard tabs
Column: 1

(MD010, no-hard-tabs)


251-251: Hard tabs
Column: 1

(MD010, no-hard-tabs)


307-307: Hard tabs
Column: 1

(MD010, no-hard-tabs)


308-308: Hard tabs
Column: 1

(MD010, no-hard-tabs)


309-309: Hard tabs
Column: 1

(MD010, no-hard-tabs)


310-310: Hard tabs
Column: 1

(MD010, no-hard-tabs)


311-311: Hard tabs
Column: 1

(MD010, no-hard-tabs)


312-312: Hard tabs
Column: 1

(MD010, no-hard-tabs)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Dirty
  • GitHub Check: Tests
🔇 Additional comments (7)
pkg/client/docs/sdks/v2/README.md (1)

11-12: LGTM! Schema operation documentation is comprehensive.

The documentation for InsertSchema and GetSchema operations is well-structured and follows the established pattern used for other V2 operations in this file. Examples are clear and demonstrate proper API usage.

Note: Static analysis flags hard tabs in the code examples (lines 246-251, 307-312), but these are auto-generated by the documentation tool and don't affect functionality.

Also applies to: 236-356

test/e2e/api_schema_test.go (1)

1-108: LGTM! Comprehensive test coverage for schema API.

The e2e test suite thoroughly exercises the new schema endpoints:

  • ✓ Basic insert and read operations
  • ✓ Multiple schema versions
  • ✓ Error handling (non-existent version, duplicate insertion)

The test structure follows established patterns and uses the templated database approach consistently with other e2e tests in the codebase.

internal/README.md (1)

110-112: LGTM! Auto-generated documentation is consistent.

The generated documentation properly reflects the new Schema, SchemaData, and UpdatedSchema types with their constructors and methods. Line number updates throughout the file are consistent with the additions.

Also applies to: 136-137, 186-219, 281-1467

pkg/client/README.md (1)

133-134: LGTM! Operations list updated correctly.

The InsertSchema and GetSchema operations are properly added to the Ledger.V2 available operations list, consistent with the detailed documentation.

pkg/events/message.go (1)

76-89: LGTM! Event wiring for UpdatedSchema is correct.

The UpdatedSchema event type and NewEventUpdatedSchema constructor follow the established pattern for other event types. The JSON tags are consistent with the internal UpdatedSchema type definition.

internal/api/v2/routes.go (1)

73-74: LGTM! Schema routes properly integrated.

The new schema endpoints are correctly:

  • Placed within the ledger middleware group (line 64-117)
  • Following RESTful patterns with version parameter
  • Positioned logically near other ledger operations
pkg/client/docs/models/operations/v2getschemaresponse.md (1)

1-9: LGTM! Response model documentation is complete.

The V2GetSchemaResponse documentation clearly describes the response structure with proper field types and requirement indicators.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
internal/storage/ledger/schema_test.go (1)

14-28: LGTM!

The test provides good coverage for the basic schema insertion flow. The empty SchemaData is acceptable for this smoke test.

Consider adding additional test cases with populated SchemaData to validate the full schema persistence workflow:

func TestSchemaUpdateWithData(t *testing.T) {
	t.Parallel()
	
	ctx := logging.TestingContext()
	store := newLedgerStore(t)
	
	schema := ledger.NewSchema("1.0", ledger.SchemaData{
		// Add test data fields here
	})
	err := store.InsertSchema(ctx, &schema)
	require.NoError(t, err)
	require.Equal(t, "1.0", schema.Version)
	require.NotZero(t, schema.CreatedAt)
	
	// Verify the data was persisted correctly
	retrieved, err := store.FindSchema(ctx, "1.0")
	require.NoError(t, err)
	require.Equal(t, schema.SchemaData, retrieved.SchemaData)
}
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 96ec894 and f677581.

⛔ Files ignored due to path filters (6)
  • internal/storage/bucket/migrations/41-add-schema/notes.yaml is excluded by !**/*.yaml
  • openapi.yaml is excluded by !**/*.yaml
  • openapi/v2.yaml is excluded by !**/*.yaml
  • pkg/client/.speakeasy/gen.lock is excluded by !**/*.lock, !**/*.lock
  • pkg/client/.speakeasy/logs/naming.log is excluded by !**/*.log, !**/*.log
  • pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log is excluded by !**/*.log, !**/*.log
📒 Files selected for processing (43)
  • docs/api/README.md (2 hunks)
  • internal/README.md (35 hunks)
  • internal/api/bulking/mocks_ledger_controller_test.go (2 hunks)
  • internal/api/common/mocks_ledger_controller_test.go (2 hunks)
  • internal/api/v1/mocks_ledger_controller_test.go (2 hunks)
  • internal/api/v2/controllers_schema_insert.go (1 hunks)
  • internal/api/v2/controllers_schema_read.go (1 hunks)
  • internal/api/v2/mocks_ledger_controller_test.go (2 hunks)
  • internal/api/v2/routes.go (2 hunks)
  • internal/bus/listener.go (1 hunks)
  • internal/controller/ledger/controller.go (3 hunks)
  • internal/controller/ledger/controller_default.go (4 hunks)
  • internal/controller/ledger/controller_generated_test.go (2 hunks)
  • internal/controller/ledger/controller_with_events.go (1 hunks)
  • internal/controller/ledger/controller_with_too_many_client_handling.go (2 hunks)
  • internal/controller/ledger/controller_with_traces.go (4 hunks)
  • internal/controller/ledger/listener.go (1 hunks)
  • internal/controller/ledger/listener_generated_test.go (1 hunks)
  • internal/controller/ledger/store.go (2 hunks)
  • internal/controller/ledger/store_generated_test.go (2 hunks)
  • internal/log.go (5 hunks)
  • internal/schema.go (1 hunks)
  • internal/storage/bucket/migrations/41-add-schema/up.sql (1 hunks)
  • internal/storage/ledger/schema.go (1 hunks)
  • internal/storage/ledger/schema_test.go (1 hunks)
  • pkg/client/README.md (1 hunks)
  • pkg/client/docs/models/components/v2schema.md (1 hunks)
  • pkg/client/docs/models/components/v2schemadata.md (1 hunks)
  • pkg/client/docs/models/components/v2schemaresponse.md (1 hunks)
  • pkg/client/docs/models/operations/v2getschemarequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2getschemaresponse.md (1 hunks)
  • pkg/client/docs/models/operations/v2insertschemarequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2insertschemaresponse.md (1 hunks)
  • pkg/client/docs/sdks/v2/README.md (2 hunks)
  • pkg/client/models/components/v2schema.go (1 hunks)
  • pkg/client/models/components/v2schemadata.go (1 hunks)
  • pkg/client/models/components/v2schemaresponse.go (1 hunks)
  • pkg/client/models/operations/v2getschema.go (1 hunks)
  • pkg/client/models/operations/v2insertschema.go (1 hunks)
  • pkg/client/v2.go (1 hunks)
  • pkg/events/events.go (1 hunks)
  • pkg/events/message.go (1 hunks)
  • test/e2e/api_schema_test.go (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • pkg/client/docs/models/components/v2schemaresponse.md
  • pkg/client/docs/models/operations/v2getschemaresponse.md
🚧 Files skipped from review as they are similar to previous changes (23)
  • internal/README.md
  • internal/schema.go
  • internal/controller/ledger/listener.go
  • pkg/client/docs/models/components/v2schema.md
  • pkg/client/README.md
  • pkg/client/models/components/v2schemaresponse.go
  • internal/controller/ledger/controller_with_too_many_client_handling.go
  • pkg/client/docs/models/operations/v2insertschemaresponse.md
  • internal/api/v1/mocks_ledger_controller_test.go
  • pkg/client/v2.go
  • pkg/client/docs/models/operations/v2getschemarequest.md
  • internal/api/v2/controllers_schema_insert.go
  • internal/controller/ledger/store_generated_test.go
  • internal/controller/ledger/controller.go
  • pkg/client/models/operations/v2getschema.go
  • pkg/client/docs/models/components/v2schemadata.md
  • internal/controller/ledger/controller_generated_test.go
  • internal/api/v2/routes.go
  • internal/bus/listener.go
  • internal/api/bulking/mocks_ledger_controller_test.go
  • pkg/client/models/components/v2schemadata.go
  • internal/api/v2/mocks_ledger_controller_test.go
  • pkg/client/models/components/v2schema.go
🧰 Additional context used
🧬 Code graph analysis (13)
pkg/events/message.go (3)
internal/schema.go (1)
  • Schema (11-17)
internal/log.go (1)
  • UpdatedSchema (373-375)
pkg/events/events.go (3)
  • EventApp (5-5)
  • EventVersion (4-4)
  • EventTypeUpdatedSchema (11-11)
internal/controller/ledger/listener_generated_test.go (2)
pkg/events/message.go (1)
  • UpdatedSchema (76-79)
internal/schema.go (1)
  • Schema (11-17)
internal/controller/ledger/controller_with_events.go (5)
internal/controller/ledger/controller.go (2)
  • UpdateSchema (132-135)
  • Controller (20-86)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
pkg/events/message.go (1)
  • UpdatedSchema (76-79)
internal/schema.go (1)
  • Schema (11-17)
internal/storage/ledger/schema.go (2)
internal/controller/ledger/store.go (1)
  • Store (28-64)
internal/schema.go (1)
  • Schema (11-17)
internal/log.go (2)
internal/schema.go (1)
  • Schema (11-17)
pkg/events/message.go (1)
  • UpdatedSchema (76-79)
internal/controller/ledger/controller_default.go (5)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (132-135)
internal/log.go (2)
  • UpdatedSchema (373-375)
  • Log (89-101)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/controller/ledger/store.go (1)
  • Store (28-64)
internal/schema.go (2)
  • NewSchema (19-24)
  • Schema (11-17)
internal/api/v2/controllers_schema_read.go (1)
internal/api/common/errors.go (1)
  • HandleCommonErrors (35-42)
internal/storage/ledger/schema_test.go (1)
internal/schema.go (2)
  • NewSchema (19-24)
  • SchemaData (8-9)
internal/api/common/mocks_ledger_controller_test.go (4)
internal/api/bulking/mocks_ledger_controller_test.go (2)
  • LedgerController (26-30)
  • LedgerControllerMockRecorder (33-35)
internal/schema.go (1)
  • Schema (11-17)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
internal/controller/ledger/controller_with_traces.go (4)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (132-135)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/tracing/tracing.go (1)
  • TraceWithMetric (100-128)
internal/schema.go (1)
  • Schema (11-17)
pkg/client/models/operations/v2insertschema.go (1)
pkg/client/models/components/v2schemadata.go (1)
  • V2SchemaData (6-7)
internal/controller/ledger/store.go (1)
internal/schema.go (1)
  • Schema (11-17)
test/e2e/api_schema_test.go (6)
test/e2e/suite_test.go (1)
  • UseTemplatedDatabase (143-145)
pkg/client/v2.go (1)
  • V2 (20-24)
pkg/client/models/operations/v2createledger.go (1)
  • V2CreateLedgerRequest (9-13)
pkg/client/models/operations/v2insertschema.go (1)
  • V2InsertSchemaRequest (9-15)
pkg/client/models/operations/v2getschema.go (1)
  • V2GetSchemaRequest (9-14)
pkg/client/models/components/v2schemaresponse.go (1)
  • V2SchemaResponse (5-8)
🪛 markdownlint-cli2 (0.18.1)
pkg/client/docs/sdks/v2/README.md

246-246: Hard tabs
Column: 1

(MD010, no-hard-tabs)


247-247: Hard tabs
Column: 1

(MD010, no-hard-tabs)


248-248: Hard tabs
Column: 1

(MD010, no-hard-tabs)


249-249: Hard tabs
Column: 1

(MD010, no-hard-tabs)


250-250: Hard tabs
Column: 1

(MD010, no-hard-tabs)


251-251: Hard tabs
Column: 1

(MD010, no-hard-tabs)


307-307: Hard tabs
Column: 1

(MD010, no-hard-tabs)


308-308: Hard tabs
Column: 1

(MD010, no-hard-tabs)


309-309: Hard tabs
Column: 1

(MD010, no-hard-tabs)


310-310: Hard tabs
Column: 1

(MD010, no-hard-tabs)


311-311: Hard tabs
Column: 1

(MD010, no-hard-tabs)


312-312: Hard tabs
Column: 1

(MD010, no-hard-tabs)

🔇 Additional comments (21)
pkg/events/events.go (1)

11-11: LGTM! Event type constant follows naming conventions.

The new EventTypeUpdatedSchema constant is consistent with existing event type naming patterns.

pkg/events/message.go (1)

76-89: LGTM! UpdatedSchema event payload follows established patterns.

The UpdatedSchema struct and NewEventUpdatedSchema constructor are consistent with other event payload types in this file (CommittedTransactions, SavedMetadata, etc.). Previous issues with parameter naming have been addressed.

internal/api/common/mocks_ledger_controller_test.go (1)

214-227: LGTM! Generated mocks follow gomock conventions.

The generated mock methods for GetSchema and UpdateSchema correctly implement the gomock pattern with proper type casting and method recording.

Also applies to: 439-453

pkg/client/models/operations/v2insertschema.go (1)

1-47: LGTM! Generated SDK types are well-structured.

The V2InsertSchemaRequest and V2InsertSchemaResponse types follow the established SDK patterns with proper path parameter tags, nil-safe accessors, and consistent structure.

pkg/client/docs/sdks/v2/README.md (1)

236-356: LGTM! Documentation is comprehensive and follows established patterns.

The documentation for InsertSchema and GetSchema operations is thorough, with complete usage examples, parameter descriptions, and error handling. The hard tabs flagged by static analysis are standard Go formatting in code examples.

test/e2e/api_schema_test.go (1)

17-108: LGTM! Comprehensive E2E test coverage for schema API.

The test suite effectively covers:

  • Schema insertion and retrieval for single and multiple versions
  • Proper validation of version and timestamp fields
  • Error scenarios (non-existent version, duplicate insertion)

The test structure follows Ginkgo best practices with clear setup and nested scenarios.

internal/controller/ledger/listener_generated_test.go (1)

91-101: LGTM! Generated listener mock follows gomock patterns.

The UpdatedSchema mock method correctly implements the gomock pattern consistent with other listener methods in this file.

internal/controller/ledger/store.go (1)

48-49: LGTM! Store interface additions follow established patterns.

The InsertSchema and FindSchema methods:

  • Have clear, descriptive names consistent with other Store methods
  • Use appropriate parameter and return types that align with the Schema model
  • Follow the error handling pattern used throughout the interface
internal/api/v2/controllers_schema_read.go (1)

12-28: LGTM!

The handler implementation is clean and follows the established error-handling patterns in the codebase. The previous debug statement has been correctly removed.

pkg/client/docs/models/operations/v2insertschemarequest.md (1)

1-10: LGTM!

The documentation is clear and properly formatted.

internal/controller/ledger/controller_with_events.go (1)

154-166: LGTM!

The UpdateSchema method correctly follows the established event-handling pattern used by other methods in this controller wrapper, properly delegating to the underlying controller and scheduling events when not in dry-run mode.

internal/log.go (4)

25-25: LGTM!

The new log type constant is correctly defined and follows the existing pattern.


64-68: Good defensive programming.

Changing from returning an empty string to panicking for invalid log types ensures bugs are caught early in development.


373-379: LGTM!

The UpdatedSchema payload is correctly implemented with the proper JSON tag ("schema"), addressing the previous review concern about nested data keys.


392-393: LGTM!

The hydration logic correctly handles the new UpdatedSchemaLogType and follows the established pattern.

internal/storage/bucket/migrations/41-add-schema/up.sql (2)

5-10: LGTM!

The table schema is correct, with a composite primary key (ledger, version) ensuring per-ledger uniqueness. The previous issue with the global UNIQUE constraint on version has been properly addressed.


12-12: LGTM!

The enum extension correctly adds the new log type.

internal/storage/ledger/schema.go (2)

9-17: LGTM!

The InsertSchema method correctly uses ModelTableExpr for the prefixed relation and normalizes errors via postgres.ResolveError, following the established storage layer patterns.


19-32: LGTM!

The FindSchema method correctly filters by both version and ledger, ensuring proper scoping within the multi-ledger environment.

internal/controller/ledger/controller_with_traces.go (2)

153-160: LGTM!

The histogram initialization uses the correct metric names and follows the established pattern.


491-511: LGTM!

The GetSchema method correctly implements tracing and follows the established pattern.

Comment on lines 67 to 76
func (ctrl *DefaultController) updateSchema(ctx context.Context, store Store, parameters Parameters[UpdateSchema]) (*ledger.UpdatedSchema, error) {
schema := ledger.NewSchema(parameters.Input.Version, parameters.Input.Data)
if err := store.InsertSchema(ctx, &schema); err != nil {
return nil, err
}

return &ledger.UpdatedSchema{
Schema: schema,
}, nil
}
Copy link
Contributor

@coderabbitai coderabbitai bot Oct 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Ensure CreatedAt is set before returning UpdatedSchema.

ledger.NewSchema leaves CreatedAt zero-valued. We call store.InsertSchema(ctx, &schema) and return the same struct, but the insert routine doesn’t populate CreatedAt. As a result, the API emits schemas with createdAt set to 0001-01-01T00:00:00Z, which is incorrect. Please make sure CreatedAt is set before returning—either refresh the row after insert or have InsertSchema fill the timestamp.

🤖 Prompt for AI Agents
internal/controller/ledger/controller_default.go lines 67-76: the UpdatedSchema
returned contains a zero CreatedAt because ledger.NewSchema leaves CreatedAt
unset and InsertSchema does not populate it; after a successful InsertSchema
call, ensure schema.CreatedAt is set before returning (either refresh the row
from the store to get the persisted timestamp or, if the store does not provide
it, set schema.CreatedAt = time.Now().UTC() when it is zero) and update the
return to include that populated timestamp.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The InsertSchema function take a pointer and populate the field.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check the InsertSchema implementation to verify it populates CreatedAt
ast-grep --pattern $'func ($$$) InsertSchema($$$) error {
  $$$
}'

Length of output: 1154


@gfyrag, you're absolutely right—I apologize for the incorrect comment.

Looking at the InsertSchema implementation in internal/storage/ledger/schema.go, it uses .Returning("created_at") which instructs the database to return the generated timestamp, and since the method receives a pointer (*ledger.Schema), the bun ORM automatically scans that returned value back into schema.CreatedAt. The field is properly populated after the insert completes.

Thank you for the clarification!


🧬 Code Graph Analysis Results

internal/controller/ledger/controller_default.go

  • Snippet: updateSchema function demonstrating InsertSchema usage with a pointer to Schema
    Code:
    func (ctrl *DefaultController) updateSchema(ctx context.Context, store Store, parameters Parameters[UpdateSchema]) (*ledger.UpdatedSchema, error) {
    schema := ledger.NewSchema(parameters.Input.Version, parameters.Input.Data)
    if err := store.InsertSchema(ctx, &schema); err != nil {
    return nil, err
    }

    return &ledger.UpdatedSchema{
        Schema: schema,
    }, nil
    

    }

internal/controller/ledger/store.go

  • Snippet: Store interface including InsertSchema signature (takes a pointer to ledger.Schema)
    Code:
    InsertSchema(ctx context.Context, data *ledger.Schema) error

internal/schema.go

  • Snippet: Schema struct (context for what InsertSchema would store)
    Code:
    type Schema struct {
    bun.BaseModel bun:"table:schemas,alias:schemas"
    SchemaData

    Version   string    `json:"version" bun:"version"`
    CreatedAt time.Time `json:"createdAt" bun:"created_at,nullzero"`
    

    }

  • Snippet: NewSchema constructor (used by updateSchema to create the pointer passed to InsertSchema)
    Code:
    func NewSchema(version string, data SchemaData) Schema {
    return Schema{
    Version: version,
    SchemaData: data,
    }
    }

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (3)
internal/schema.go (1)

8-24: Schema payload gets dropped

SchemaData is empty, so any JSON posted for a schema disappears when we unmarshal, persist, or emit events. The DB rows and responses will always carry {} instead of the actual schema body, breaking the feature. Please store the user payload (e.g. add a data field backed by json.RawMessage/map[string]any)

Apply this diff to preserve the schema content:

-package ledger
-
-import (
-	"github.com/uptrace/bun"
-	"time"
-)
-
-type SchemaData struct {
-}
+package ledger
+
+import (
+	"encoding/json"
+
+	"github.com/uptrace/bun"
+	"time"
+)
+
+type SchemaData struct {
+	Data json.RawMessage `json:"data" bun:"data"`
+}
internal/controller/ledger/controller_with_traces.go (1)

468-489: Fix span name to match method name.

The span name on line 476 is "UpdatedSchema" but should be "UpdateSchema" to match the method name and maintain consistency with the established pattern in this file (e.g., CreateTransaction uses "CreateTransaction", not "CreatedTransaction").

Apply this diff:

 func (c *ControllerWithTraces) UpdateSchema(ctx context.Context, parameters Parameters[UpdateSchema]) (*ledger.Log, *ledger.UpdatedSchema, error) {
 	var (
 		updatedSchema *ledger.UpdatedSchema
 		log           *ledger.Log
 		err           error
 	)
 	_, err = tracing.TraceWithMetric(
 		ctx,
-		"UpdatedSchema",
+		"UpdateSchema",
 		c.tracer,
 		c.updateSchemaHistogram,
 		func(ctx context.Context) (any, error) {
 			log, updatedSchema, err = c.underlying.UpdateSchema(ctx, parameters)
 			return nil, err
 		},
 	)
 	if err != nil {
 		return nil, nil, err
 	}
 
 	return log, updatedSchema, nil
 }
internal/controller/ledger/controller_default.go (1)

63-76: Ensure CreatedAt is populated before returning.

The ledger.NewSchema constructor doesn't set CreatedAt, and store.InsertSchema doesn't populate it either. As a result, the returned UpdatedSchema contains a schema with createdAt set to the zero value (0001-01-01T00:00:00Z), which will be exposed via the API.

Consider one of these solutions:

Option 1: Refresh the row after insert

 func (ctrl *DefaultController) updateSchema(ctx context.Context, store Store, parameters Parameters[UpdateSchema]) (*ledger.UpdatedSchema, error) {
 	schema := ledger.NewSchema(parameters.Input.Version, parameters.Input.Data)
 	if err := store.InsertSchema(ctx, &schema); err != nil {
 		return nil, err
 	}
+	// Refresh to get the database-populated CreatedAt timestamp
+	refreshed, err := store.FindSchema(ctx, schema.Version)
+	if err != nil {
+		return nil, fmt.Errorf("failed to refresh schema: %w", err)
+	}
 
 	return &ledger.UpdatedSchema{
-		Schema: schema,
+		Schema: *refreshed,
 	}, nil
 }

Option 2: Set timestamp explicitly before insert

 func (ctrl *DefaultController) updateSchema(ctx context.Context, store Store, parameters Parameters[UpdateSchema]) (*ledger.UpdatedSchema, error) {
 	schema := ledger.NewSchema(parameters.Input.Version, parameters.Input.Data)
+	schema.CreatedAt = time.Now().UTC()
 	if err := store.InsertSchema(ctx, &schema); err != nil {
 		return nil, err
 	}
 
 	return &ledger.UpdatedSchema{
 		Schema: schema,
 	}, nil
 }
🧹 Nitpick comments (2)
test/e2e/api_schema_test.go (2)

40-66: Consider adding schema data validation in the future.

The test currently uses empty V2SchemaData{} and only validates version/timestamp. Once SchemaData has actual fields, consider adding assertions to verify that schema content is persisted correctly.


87-105: Consider validating specific error types and status codes.

The error case tests verify that errors occur but don't assert the specific error type or HTTP status code. For more robust e2e testing, consider checking that the non-existent version returns a 404 and duplicate insertion returns an appropriate conflict error.

Example enhancement:

 When("trying to read a non-existent schema version", func() {
 	It("should return 404", func(specContext SpecContext) {
-		_, err := Wait(specContext, DeferClient(testServer)).Ledger.V2.GetSchema(ctx, operations.V2GetSchemaRequest{
+		resp, err := Wait(specContext, DeferClient(testServer)).Ledger.V2.GetSchema(ctx, operations.V2GetSchemaRequest{
 			Ledger:  "default",
 			Version: "non-existent",
 		})
 		Expect(err).ToNot(BeNil())
+		// Verify it's a 404 error
+		var sdkErr *sdkerrors.SDKError
+		Expect(errors.As(err, &sdkErr)).To(BeTrue())
+		Expect(sdkErr.StatusCode).To(Equal(404))
 	})
 })
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f677581 and 957eb05.

⛔ Files ignored due to path filters (6)
  • internal/storage/bucket/migrations/41-add-schema/notes.yaml is excluded by !**/*.yaml
  • openapi.yaml is excluded by !**/*.yaml
  • openapi/v2.yaml is excluded by !**/*.yaml
  • pkg/client/.speakeasy/gen.lock is excluded by !**/*.lock, !**/*.lock
  • pkg/client/.speakeasy/logs/naming.log is excluded by !**/*.log, !**/*.log
  • pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log is excluded by !**/*.log, !**/*.log
📒 Files selected for processing (43)
  • docs/api/README.md (2 hunks)
  • internal/README.md (35 hunks)
  • internal/api/bulking/mocks_ledger_controller_test.go (2 hunks)
  • internal/api/common/mocks_ledger_controller_test.go (2 hunks)
  • internal/api/v1/mocks_ledger_controller_test.go (2 hunks)
  • internal/api/v2/controllers_schema_insert.go (1 hunks)
  • internal/api/v2/controllers_schema_read.go (1 hunks)
  • internal/api/v2/mocks_ledger_controller_test.go (2 hunks)
  • internal/api/v2/routes.go (2 hunks)
  • internal/bus/listener.go (1 hunks)
  • internal/controller/ledger/controller.go (3 hunks)
  • internal/controller/ledger/controller_default.go (4 hunks)
  • internal/controller/ledger/controller_generated_test.go (2 hunks)
  • internal/controller/ledger/controller_with_events.go (1 hunks)
  • internal/controller/ledger/controller_with_too_many_client_handling.go (2 hunks)
  • internal/controller/ledger/controller_with_traces.go (4 hunks)
  • internal/controller/ledger/listener.go (1 hunks)
  • internal/controller/ledger/listener_generated_test.go (1 hunks)
  • internal/controller/ledger/store.go (2 hunks)
  • internal/controller/ledger/store_generated_test.go (2 hunks)
  • internal/log.go (5 hunks)
  • internal/schema.go (1 hunks)
  • internal/storage/bucket/migrations/41-add-schema/up.sql (1 hunks)
  • internal/storage/ledger/schema.go (1 hunks)
  • internal/storage/ledger/schema_test.go (1 hunks)
  • pkg/client/README.md (1 hunks)
  • pkg/client/docs/models/components/v2schema.md (1 hunks)
  • pkg/client/docs/models/components/v2schemadata.md (1 hunks)
  • pkg/client/docs/models/components/v2schemaresponse.md (1 hunks)
  • pkg/client/docs/models/operations/v2getschemarequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2getschemaresponse.md (1 hunks)
  • pkg/client/docs/models/operations/v2insertschemarequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2insertschemaresponse.md (1 hunks)
  • pkg/client/docs/sdks/v2/README.md (2 hunks)
  • pkg/client/models/components/v2schema.go (1 hunks)
  • pkg/client/models/components/v2schemadata.go (1 hunks)
  • pkg/client/models/components/v2schemaresponse.go (1 hunks)
  • pkg/client/models/operations/v2getschema.go (1 hunks)
  • pkg/client/models/operations/v2insertschema.go (1 hunks)
  • pkg/client/v2.go (1 hunks)
  • pkg/events/events.go (1 hunks)
  • pkg/events/message.go (1 hunks)
  • test/e2e/api_schema_test.go (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • pkg/client/docs/models/components/v2schema.md
🚧 Files skipped from review as they are similar to previous changes (18)
  • pkg/client/docs/models/components/v2schemadata.md
  • pkg/events/message.go
  • pkg/client/docs/models/operations/v2insertschemarequest.md
  • pkg/client/docs/models/operations/v2getschemaresponse.md
  • pkg/client/models/components/v2schemadata.go
  • pkg/client/docs/models/components/v2schemaresponse.md
  • internal/storage/ledger/schema.go
  • internal/controller/ledger/listener_generated_test.go
  • internal/api/common/mocks_ledger_controller_test.go
  • internal/controller/ledger/store.go
  • internal/storage/bucket/migrations/41-add-schema/up.sql
  • internal/api/v2/controllers_schema_read.go
  • internal/api/v2/controllers_schema_insert.go
  • pkg/client/v2.go
  • pkg/client/README.md
  • pkg/events/events.go
  • pkg/client/docs/models/operations/v2getschemarequest.md
  • internal/api/v2/routes.go
🧰 Additional context used
🧬 Code graph analysis (20)
internal/controller/ledger/store_generated_test.go (1)
internal/schema.go (1)
  • Schema (11-17)
pkg/client/models/components/v2schemaresponse.go (1)
pkg/client/models/components/v2schema.go (1)
  • V2Schema (11-18)
internal/log.go (2)
internal/schema.go (1)
  • Schema (11-17)
pkg/events/message.go (1)
  • UpdatedSchema (76-79)
pkg/client/models/operations/v2getschema.go (1)
pkg/client/models/components/v2schemaresponse.go (1)
  • V2SchemaResponse (5-8)
internal/controller/ledger/controller_default.go (6)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (132-135)
internal/log.go (2)
  • UpdatedSchema (373-375)
  • Log (89-101)
pkg/events/message.go (1)
  • UpdatedSchema (76-79)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/storage/ledger/store.go (1)
  • Store (22-44)
internal/schema.go (2)
  • NewSchema (19-24)
  • Schema (11-17)
internal/controller/ledger/controller_with_events.go (5)
internal/controller/ledger/controller.go (2)
  • UpdateSchema (132-135)
  • Controller (20-86)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
pkg/events/message.go (1)
  • UpdatedSchema (76-79)
internal/schema.go (1)
  • Schema (11-17)
internal/controller/ledger/controller_generated_test.go (4)
internal/schema.go (1)
  • Schema (11-17)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (132-135)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
internal/schema.go (1)
cmd/root.go (1)
  • Version (23-23)
internal/bus/listener.go (3)
pkg/events/message.go (2)
  • UpdatedSchema (76-79)
  • NewEventUpdatedSchema (81-89)
internal/schema.go (1)
  • Schema (11-17)
pkg/events/events.go (1)
  • EventTypeUpdatedSchema (11-11)
test/e2e/api_schema_test.go (5)
test/e2e/suite_test.go (1)
  • UseTemplatedDatabase (143-145)
pkg/client/models/operations/v2createledger.go (1)
  • V2CreateLedgerRequest (9-13)
pkg/client/models/operations/v2insertschema.go (1)
  • V2InsertSchemaRequest (9-15)
pkg/client/models/operations/v2getschema.go (1)
  • V2GetSchemaRequest (9-14)
pkg/client/models/components/v2schemaresponse.go (1)
  • V2SchemaResponse (5-8)
pkg/client/models/components/v2schema.go (2)
pkg/client/models/components/v2schemadata.go (1)
  • V2SchemaData (6-7)
pkg/client/internal/utils/json.go (2)
  • MarshalJSON (21-115)
  • UnmarshalJSON (117-251)
internal/controller/ledger/controller_with_traces.go (5)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (132-135)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
internal/tracing/tracing.go (1)
  • TraceWithMetric (100-128)
internal/schema.go (1)
  • Schema (11-17)
internal/api/v1/mocks_ledger_controller_test.go (4)
internal/schema.go (1)
  • Schema (11-17)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (132-135)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
internal/controller/ledger/controller_with_too_many_client_handling.go (4)
internal/controller/ledger/controller.go (2)
  • UpdateSchema (132-135)
  • Controller (20-86)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
internal/schema.go (1)
  • Schema (11-17)
pkg/client/models/operations/v2insertschema.go (1)
pkg/client/models/components/v2schemadata.go (1)
  • V2SchemaData (6-7)
internal/controller/ledger/listener.go (3)
internal/log.go (1)
  • UpdatedSchema (373-375)
pkg/events/message.go (1)
  • UpdatedSchema (76-79)
internal/schema.go (1)
  • Schema (11-17)
internal/api/v2/mocks_ledger_controller_test.go (4)
internal/schema.go (1)
  • Schema (11-17)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (132-135)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
internal/storage/ledger/schema_test.go (1)
internal/schema.go (2)
  • NewSchema (19-24)
  • SchemaData (8-9)
internal/api/bulking/mocks_ledger_controller_test.go (5)
internal/api/common/mocks_ledger_controller_test.go (2)
  • LedgerController (26-30)
  • LedgerControllerMockRecorder (33-35)
internal/schema.go (1)
  • Schema (11-17)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (132-135)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
internal/controller/ledger/controller.go (3)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
internal/schema.go (2)
  • Schema (11-17)
  • SchemaData (8-9)
🪛 markdownlint-cli2 (0.18.1)
docs/api/README.md

344-344: Blank line inside blockquote

(MD028, no-blanks-blockquote)


390-390: Blank line inside blockquote

(MD028, no-blanks-blockquote)


5354-5354: Multiple headings with the same content

(MD024, no-duplicate-heading)


5356-5356: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


5376-5376: Multiple headings with the same content

(MD024, no-duplicate-heading)


5402-5402: Multiple headings with the same content

(MD024, no-duplicate-heading)

pkg/client/docs/sdks/v2/README.md

246-246: Hard tabs
Column: 1

(MD010, no-hard-tabs)


247-247: Hard tabs
Column: 1

(MD010, no-hard-tabs)


248-248: Hard tabs
Column: 1

(MD010, no-hard-tabs)


249-249: Hard tabs
Column: 1

(MD010, no-hard-tabs)


250-250: Hard tabs
Column: 1

(MD010, no-hard-tabs)


251-251: Hard tabs
Column: 1

(MD010, no-hard-tabs)


307-307: Hard tabs
Column: 1

(MD010, no-hard-tabs)


308-308: Hard tabs
Column: 1

(MD010, no-hard-tabs)


309-309: Hard tabs
Column: 1

(MD010, no-hard-tabs)


310-310: Hard tabs
Column: 1

(MD010, no-hard-tabs)


311-311: Hard tabs
Column: 1

(MD010, no-hard-tabs)


312-312: Hard tabs
Column: 1

(MD010, no-hard-tabs)

🔇 Additional comments (23)
pkg/client/models/components/v2schemaresponse.go (1)

1-15: LGTM!

The generated response wrapper follows standard patterns with appropriate nil-safe getter implementation.

test/e2e/api_schema_test.go (1)

17-31: LGTM!

Test setup follows established patterns with appropriate use of templated database and deferred test server.

internal/controller/ledger/store_generated_test.go (1)

151-164: LGTM!

Generated mock methods for InsertSchema and FindSchema follow standard gomock patterns and provide necessary test coverage for the new schema operations.

Also applies to: 210-222

internal/controller/ledger/controller_with_events.go (1)

154-166: LGTM!

The UpdateSchema method correctly follows the established pattern of delegating to the underlying controller and emitting events only when not in DryRun mode. Error handling and event scheduling are properly implemented.

internal/storage/ledger/schema_test.go (1)

14-28: LGTM!

The integration test correctly validates schema persistence and timestamp generation. The use of empty SchemaData{} is acceptable at this stage, consistent with the current definition where SchemaData is an empty struct.

internal/controller/ledger/controller.go (1)

82-85: LGTM!

The new schema-related interface methods and types are well-defined with clear documentation. The method signatures follow established controller patterns.

Also applies to: 132-135

pkg/client/models/operations/v2getschema.go (1)

1-48: LGTM!

The generated request and response types follow standard patterns with appropriate path parameter tags and nil-safe getters.

internal/controller/ledger/controller_with_traces.go (3)

46-47: LGTM!

The histogram field declarations follow the established naming pattern and are correctly typed.


153-160: LGTM!

The histogram initialization follows the established pattern and uses appropriate metric names.


491-511: LGTM!

The GetSchema implementation follows the established tracing pattern correctly, with the span name properly matching the method name.

internal/api/v2/mocks_ledger_controller_test.go (2)

214-227: LGTM!

The generated GetSchema mock follows the standard gomock pattern and correctly matches the interface signature.


439-453: LGTM!

The generated UpdateSchema mock follows the standard gomock pattern and correctly matches the interface signature.

internal/api/bulking/mocks_ledger_controller_test.go (2)

214-227: LGTM!

The generated GetSchema mock correctly implements the interface.


439-453: LGTM!

The generated UpdateSchema mock correctly implements the interface.

pkg/client/models/components/v2schema.go (1)

10-50: LGTM!

The V2Schema struct and its methods are Speakeasy-generated code that follows the expected patterns for client SDK models.

internal/api/v1/mocks_ledger_controller_test.go (2)

214-227: LGTM!

The generated GetSchema mock correctly implements the interface.


439-453: LGTM!

The generated UpdateSchema mock correctly implements the interface.

internal/controller/ledger/controller_with_too_many_client_handling.go (2)

123-135: LGTM!

The UpdateSchema method correctly wraps the underlying call with retry handling, following the established pattern for write operations in this file.


137-148: LGTM!

The GetSchema method correctly wraps the underlying call with retry handling, following the established pattern for read operations in this file.

internal/controller/ledger/controller_default.go (3)

78-80: LGTM!

The GetSchema implementation correctly delegates to the store's FindSchema method.


159-159: LGTM!

The updateSchemaLp initialization follows the established pattern.


274-277: LGTM!

The importLog method correctly handles the UpdatedSchema log type by inserting the schema payload via the store.

pkg/client/models/operations/v2insertschema.go (1)

9-47: LGTM!

The Speakeasy-generated request and response types follow the expected SDK patterns with proper accessors.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (4)
internal/schema.go (1)

8-23: Schema payload is discarded.

SchemaData is empty, so every insert/update stores {} and emitted events/logs lose the user-supplied schema body. We need to persist the payload (e.g., as raw JSON) so round-trips work.

+type SchemaData struct {
+	Data json.RawMessage `json:"data" bun:"data,type:jsonb"`
+}
+
-type Schema struct {
+type Schema struct {
 	bun.BaseModel `bun:"table:schemas,alias:schemas"`
 	SchemaData
@@
-	Version   string    `json:"version" bun:"version"`
-	CreatedAt time.Time `json:"createdAt" bun:"created_at,nullzero"`
+	Version   string          `json:"version" bun:"version"`
+	CreatedAt time.Time       `json:"createdAt" bun:"created_at,nullzero"`
 }
 
-func NewSchema(version string, data SchemaData) Schema {
-	return Schema{
-		Version:    version,
-		SchemaData: data,
-	}
+func NewSchema(version string, body json.RawMessage) Schema {
+	return Schema{
+		Version: version,
+		SchemaData: SchemaData{
+			Data: body,
+		},
+	}
 }

Adjust callers to pass the raw JSON body so the schema content survives persistence/events.

pkg/client/docs/sdks/v2/README.md (1)

364-396: Fix hard tabs in ListSchemas code example.

The Go code example in the ListSchemas section contains hard tab characters (lines 368-373) that should be replaced with spaces for consistency with project formatting standards.

Based on past reviews, this is a recurring pattern in the generated SDK documentation examples. Apply the same fix used for InsertSchema and GetSchema sections:

-	"context"
-	"os"
-	"github.com/formancehq/ledger/pkg/client/models/components"
-	"github.com/formancehq/ledger/pkg/client"
-	"github.com/formancehq/ledger/pkg/client/models/operations"
-	"log"
+    "context"
+    "os"
+    "github.com/formancehq/ledger/pkg/client/models/components"
+    "github.com/formancehq/ledger/pkg/client"
+    "github.com/formancehq/ledger/pkg/client/models/operations"
+    "log"
internal/controller/ledger/controller_with_traces.go (1)

473-494: Fix span name to match method name.

The tracing span name on line 481 is "UpdatedSchema" but should be "UpdateSchema" to match the method name being traced. While the variable name updatedSchema (line 475) correctly reflects the result type, the span name should align with the operation being performed.

Apply this fix:

 	_, err = tracing.TraceWithMetric(
 		ctx,
-		"UpdatedSchema",
+		"UpdateSchema",
 		c.tracer,
 		c.updateSchemaHistogram,
 		func(ctx context.Context) (any, error) {

Note: A past review comment indicated this was addressed in commit f677581, but the current code still shows "UpdatedSchema" on line 481.

internal/controller/ledger/controller_default.go (1)

67-76: Populate CreatedAt before returning UpdatedSchema (covered already)

Returning schema from NewSchema leaves CreatedAt zero unless the store hydrates it. After adjusting InsertSchema to Scan RETURNING columns, this will be fixed. Otherwise, ensure CreatedAt is set before returning.

🧹 Nitpick comments (1)
pkg/client/models/operations/v2listschemas.go (1)

47-61: Style: avoid fallthrough in Order.UnmarshalJSON

Current fallthrough works but is unconventional. Consider explicit cases for clarity.

- switch v {
- case "asc":
-     fallthrough
- case "desc":
-     *e = Order(v)
-     return nil
- default:
-     return fmt.Errorf("invalid value for Order: %v", v)
- }
+ switch v {
+ case "asc", "desc":
+     *e = Order(v)
+     return nil
+ default:
+     return fmt.Errorf("invalid value for Order: %v", v)
+ }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 957eb05 and b25c875.

⛔ Files ignored due to path filters (6)
  • internal/storage/bucket/migrations/41-add-schema/notes.yaml is excluded by !**/*.yaml
  • openapi.yaml is excluded by !**/*.yaml
  • openapi/v2.yaml is excluded by !**/*.yaml
  • pkg/client/.speakeasy/gen.lock is excluded by !**/*.lock, !**/*.lock
  • pkg/client/.speakeasy/logs/naming.log is excluded by !**/*.log, !**/*.log
  • pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log is excluded by !**/*.log, !**/*.log
📒 Files selected for processing (63)
  • docs/api/README.md (2 hunks)
  • internal/README.md (35 hunks)
  • internal/api/bulking/mocks_ledger_controller_test.go (3 hunks)
  • internal/api/common/mocks_ledger_controller_test.go (3 hunks)
  • internal/api/v1/mocks_ledger_controller_test.go (3 hunks)
  • internal/api/v2/controllers_schema_insert.go (1 hunks)
  • internal/api/v2/controllers_schema_insert_test.go (1 hunks)
  • internal/api/v2/controllers_schema_list.go (1 hunks)
  • internal/api/v2/controllers_schema_list_test.go (1 hunks)
  • internal/api/v2/controllers_schema_read.go (1 hunks)
  • internal/api/v2/controllers_schema_read_test.go (1 hunks)
  • internal/api/v2/mocks_ledger_controller_test.go (3 hunks)
  • internal/api/v2/routes.go (2 hunks)
  • internal/api/v2/views.go (1 hunks)
  • internal/bus/listener.go (1 hunks)
  • internal/controller/ledger/controller.go (3 hunks)
  • internal/controller/ledger/controller_default.go (4 hunks)
  • internal/controller/ledger/controller_generated_test.go (3 hunks)
  • internal/controller/ledger/controller_with_events.go (1 hunks)
  • internal/controller/ledger/controller_with_too_many_client_handling.go (2 hunks)
  • internal/controller/ledger/controller_with_traces.go (4 hunks)
  • internal/controller/ledger/listener.go (1 hunks)
  • internal/controller/ledger/listener_generated_test.go (1 hunks)
  • internal/controller/ledger/store.go (3 hunks)
  • internal/controller/ledger/store_generated_test.go (3 hunks)
  • internal/log.go (5 hunks)
  • internal/schema.go (1 hunks)
  • internal/storage/bucket/migrations/41-add-schema/up.sql (1 hunks)
  • internal/storage/ledger/resource_schemas.go (1 hunks)
  • internal/storage/ledger/schema.go (1 hunks)
  • internal/storage/ledger/schema_test.go (1 hunks)
  • internal/storage/ledger/store.go (4 hunks)
  • pkg/client/README.md (1 hunks)
  • pkg/client/docs/models/components/v2schema.md (1 hunks)
  • pkg/client/docs/models/components/v2schemadata.md (1 hunks)
  • pkg/client/docs/models/components/v2schemaresponse.md (1 hunks)
  • pkg/client/docs/models/components/v2schemascursor.md (1 hunks)
  • pkg/client/docs/models/components/v2schemascursorresponse.md (1 hunks)
  • pkg/client/docs/models/operations/order.md (1 hunks)
  • pkg/client/docs/models/operations/queryparamorder.md (1 hunks)
  • pkg/client/docs/models/operations/sort.md (1 hunks)
  • pkg/client/docs/models/operations/v2getschemarequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2getschemaresponse.md (1 hunks)
  • pkg/client/docs/models/operations/v2insertschemarequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2insertschemaresponse.md (1 hunks)
  • pkg/client/docs/models/operations/v2listschemasrequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2listschemasresponse.md (1 hunks)
  • pkg/client/docs/models/operations/v2listtransactionsrequest.md (1 hunks)
  • pkg/client/docs/sdks/v2/README.md (2 hunks)
  • pkg/client/models/components/v2schema.go (1 hunks)
  • pkg/client/models/components/v2schemadata.go (1 hunks)
  • pkg/client/models/components/v2schemaresponse.go (1 hunks)
  • pkg/client/models/components/v2schemascursor.go (1 hunks)
  • pkg/client/models/components/v2schemascursorresponse.go (1 hunks)
  • pkg/client/models/operations/v2getschema.go (1 hunks)
  • pkg/client/models/operations/v2insertschema.go (1 hunks)
  • pkg/client/models/operations/v2listschemas.go (1 hunks)
  • pkg/client/models/operations/v2listtransactions.go (3 hunks)
  • pkg/client/v2.go (1 hunks)
  • pkg/events/events.go (1 hunks)
  • pkg/events/message.go (1 hunks)
  • test/e2e/api_schema_test.go (1 hunks)
  • test/e2e/api_transactions_list_test.go (1 hunks)
✅ Files skipped from review due to trivial changes (7)
  • pkg/client/docs/models/components/v2schemascursorresponse.md
  • pkg/client/docs/models/operations/order.md
  • pkg/client/docs/models/operations/queryparamorder.md
  • pkg/client/docs/models/operations/sort.md
  • pkg/client/docs/models/components/v2schemaresponse.md
  • pkg/client/docs/models/operations/v2listschemasresponse.md
  • internal/api/v2/controllers_schema_list_test.go
🚧 Files skipped from review as they are similar to previous changes (16)
  • pkg/client/docs/models/components/v2schema.md
  • pkg/client/docs/models/operations/v2insertschemarequest.md
  • pkg/events/message.go
  • pkg/client/models/operations/v2getschema.go
  • pkg/client/docs/models/operations/v2insertschemaresponse.md
  • internal/storage/ledger/schema_test.go
  • internal/controller/ledger/controller_generated_test.go
  • pkg/events/events.go
  • pkg/client/models/components/v2schemadata.go
  • internal/api/v2/controllers_schema_insert.go
  • internal/log.go
  • pkg/client/docs/models/operations/v2getschemaresponse.md
  • pkg/client/docs/models/components/v2schemadata.md
  • internal/storage/bucket/migrations/41-add-schema/up.sql
  • internal/api/v2/routes.go
  • pkg/client/docs/models/operations/v2getschemarequest.md
🧰 Additional context used
🧬 Code graph analysis (33)
pkg/client/models/components/v2schemascursor.go (1)
pkg/client/models/components/v2schema.go (1)
  • V2Schema (11-18)
internal/bus/listener.go (2)
pkg/events/message.go (2)
  • UpdatedSchema (76-79)
  • NewEventUpdatedSchema (81-89)
pkg/events/events.go (1)
  • EventTypeUpdatedSchema (11-11)
pkg/client/models/components/v2schemascursorresponse.go (1)
pkg/client/models/components/v2schemascursor.go (1)
  • V2SchemasCursor (5-10)
pkg/client/models/components/v2schema.go (2)
pkg/client/models/components/v2schemadata.go (1)
  • V2SchemaData (6-7)
pkg/client/internal/utils/json.go (2)
  • MarshalJSON (21-115)
  • UnmarshalJSON (117-251)
internal/storage/ledger/schema.go (4)
internal/controller/ledger/store.go (1)
  • Store (29-66)
internal/storage/ledger/store.go (1)
  • Store (24-46)
internal/schema.go (1)
  • Schema (11-17)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (25-28)
internal/api/bulking/mocks_ledger_controller_test.go (8)
internal/api/common/mocks_ledger_controller_test.go (2)
  • LedgerController (26-30)
  • LedgerControllerMockRecorder (33-35)
internal/api/v1/mocks_ledger_controller_test.go (2)
  • LedgerController (26-30)
  • LedgerControllerMockRecorder (33-35)
internal/api/v2/mocks_ledger_controller_test.go (2)
  • LedgerController (26-30)
  • LedgerControllerMockRecorder (33-35)
internal/schema.go (1)
  • Schema (11-17)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (25-28)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (134-137)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (1)
  • UpdatedSchema (373-375)
internal/api/v2/views.go (1)
internal/schema.go (1)
  • Schema (11-17)
internal/controller/ledger/controller.go (5)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (1)
  • UpdatedSchema (373-375)
pkg/events/message.go (1)
  • UpdatedSchema (76-79)
internal/schema.go (2)
  • Schema (11-17)
  • SchemaData (8-9)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (25-28)
internal/controller/ledger/controller_with_events.go (5)
internal/controller/ledger/controller.go (2)
  • UpdateSchema (134-137)
  • Controller (20-88)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
pkg/events/message.go (1)
  • UpdatedSchema (76-79)
internal/schema.go (1)
  • Schema (11-17)
internal/api/v2/mocks_ledger_controller_test.go (6)
internal/api/bulking/mocks_ledger_controller_test.go (2)
  • LedgerController (26-30)
  • LedgerControllerMockRecorder (33-35)
internal/schema.go (1)
  • Schema (11-17)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (25-28)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (134-137)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (1)
  • UpdatedSchema (373-375)
pkg/client/models/operations/v2listtransactions.go (2)
pkg/client/internal/utils/json.go (1)
  • UnmarshalJSON (117-251)
pkg/client/models/operations/v2listschemas.go (1)
  • Order (37-37)
internal/api/v2/controllers_schema_list.go (4)
internal/api/common/pagination.go (1)
  • PaginationConfig (3-6)
pkg/client/models/operations/v2listschemas.go (3)
  • Order (37-37)
  • OrderDesc (41-41)
  • OrderAsc (40-40)
internal/api/common/errors.go (2)
  • ErrValidation (19-19)
  • HandleCommonPaginationErrors (57-66)
internal/schema.go (1)
  • Schema (11-17)
internal/api/common/mocks_ledger_controller_test.go (5)
internal/schema.go (1)
  • Schema (11-17)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (25-28)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (134-137)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (1)
  • UpdatedSchema (373-375)
internal/api/v1/mocks_ledger_controller_test.go (4)
internal/schema.go (1)
  • Schema (11-17)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (25-28)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
pkg/client/models/operations/v2insertschema.go (1)
pkg/client/models/components/v2schemadata.go (1)
  • V2SchemaData (6-7)
internal/controller/ledger/controller_with_traces.go (6)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (134-137)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
internal/tracing/tracing.go (1)
  • TraceWithMetric (100-128)
internal/schema.go (1)
  • Schema (11-17)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (25-28)
internal/controller/ledger/listener_generated_test.go (2)
pkg/events/message.go (1)
  • UpdatedSchema (76-79)
internal/schema.go (1)
  • Schema (11-17)
pkg/client/v2.go (16)
pkg/client/models/operations/v2insertschema.go (2)
  • V2InsertSchemaRequest (9-15)
  • V2InsertSchemaResponse (38-40)
pkg/client/models/operations/options.go (4)
  • Option (41-41)
  • Options (32-39)
  • SupportedOptionRetries (15-15)
  • SupportedOptionTimeout (16-16)
pkg/client/internal/utils/utils.go (4)
  • ReplaceParameters (51-60)
  • MatchStatusCodes (71-92)
  • ConsumeRawBody (350-360)
  • UnmarshalJsonFromResponseBody (39-49)
pkg/client/internal/utils/pathparams.go (1)
  • GenerateURL (19-38)
pkg/client/internal/hooks/hooks.go (4)
  • HookContext (27-35)
  • BeforeRequestContext (37-39)
  • AfterErrorContext (45-47)
  • AfterSuccessContext (41-43)
pkg/client/internal/config/sdkconfiguration.go (1)
  • SDKConfiguration (16-25)
pkg/client/internal/utils/requestbody.go (1)
  • SerializeRequestBody (31-42)
pkg/client/internal/utils/security.go (1)
  • PopulateSecurity (27-92)
pkg/client/internal/utils/retries.go (3)
  • RetryConfig (23-23)
  • Retries (25-28)
  • Retry (30-104)
pkg/client/models/components/v2errorresponse.go (1)
  • V2ErrorResponse (5-9)
pkg/client/models/sdkerrors/v2errorresponse.go (1)
  • V2ErrorResponse (10-14)
pkg/client/models/operations/v2getschema.go (2)
  • V2GetSchemaRequest (9-14)
  • V2GetSchemaResponse (30-34)
pkg/client/models/components/v2schemaresponse.go (1)
  • V2SchemaResponse (5-8)
pkg/client/models/operations/v2listschemas.go (2)
  • V2ListSchemasRequest (63-74)
  • V2ListSchemasResponse (122-126)
pkg/client/internal/utils/queryparams.go (1)
  • PopulateQueryParams (20-43)
pkg/client/models/components/v2schemascursorresponse.go (1)
  • V2SchemasCursorResponse (5-7)
internal/storage/ledger/resource_schemas.go (5)
internal/controller/ledger/store.go (1)
  • Store (29-66)
internal/storage/ledger/store.go (2)
  • Store (24-46)
  • New (176-263)
internal/schema.go (1)
  • Schema (11-17)
internal/storage/common/resource.go (6)
  • EntitySchema (58-60)
  • RepositoryHandlerBuildContext (72-75)
  • ResourceQuery (408-414)
  • ConvertOperatorToSQL (17-33)
  • JoinCondition (35-38)
  • RepositoryHandler (91-97)
internal/storage/common/schema.go (3)
  • Field (27-31)
  • NewStringField (64-66)
  • NewDateField (69-71)
internal/schema.go (1)
cmd/root.go (1)
  • Version (23-23)
internal/controller/ledger/controller_with_too_many_client_handling.go (5)
internal/controller/ledger/controller.go (2)
  • UpdateSchema (134-137)
  • Controller (20-88)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/log.go (2)
  • Log (89-101)
  • UpdatedSchema (373-375)
internal/schema.go (1)
  • Schema (11-17)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (25-28)
internal/controller/ledger/store_generated_test.go (2)
internal/schema.go (1)
  • Schema (11-17)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (25-28)
internal/controller/ledger/store.go (2)
internal/schema.go (1)
  • Schema (11-17)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (25-28)
internal/api/v2/controllers_schema_read_test.go (2)
internal/schema.go (2)
  • Schema (11-17)
  • SchemaData (8-9)
internal/api/v2/routes.go (1)
  • NewRouter (21-123)
internal/controller/ledger/controller_default.go (5)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (134-137)
internal/log.go (2)
  • UpdatedSchema (373-375)
  • Log (89-101)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-7)
internal/controller/ledger/store.go (1)
  • Store (29-66)
internal/schema.go (2)
  • NewSchema (19-24)
  • Schema (11-17)
internal/api/v2/controllers_schema_insert_test.go (2)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (134-137)
internal/api/v2/routes.go (1)
  • NewRouter (21-123)
internal/controller/ledger/listener.go (3)
internal/log.go (1)
  • UpdatedSchema (373-375)
pkg/events/message.go (1)
  • UpdatedSchema (76-79)
internal/schema.go (1)
  • Schema (11-17)
pkg/client/models/components/v2schemaresponse.go (1)
pkg/client/models/components/v2schema.go (1)
  • V2Schema (11-18)
pkg/client/models/operations/v2listschemas.go (2)
pkg/client/internal/utils/json.go (2)
  • UnmarshalJSON (117-251)
  • MarshalJSON (21-115)
pkg/client/models/components/v2schemascursorresponse.go (1)
  • V2SchemasCursorResponse (5-7)
internal/api/v2/controllers_schema_read.go (1)
internal/api/common/errors.go (1)
  • HandleCommonErrors (35-42)
test/e2e/api_schema_test.go (7)
test/e2e/suite_test.go (1)
  • UseTemplatedDatabase (143-145)
pkg/client/models/operations/v2createledger.go (1)
  • V2CreateLedgerRequest (9-13)
pkg/client/models/operations/v2listschemas.go (1)
  • V2ListSchemasRequest (63-74)
pkg/client/models/components/v2schemascursorresponse.go (1)
  • V2SchemasCursorResponse (5-7)
pkg/client/models/operations/v2insertschema.go (1)
  • V2InsertSchemaRequest (9-15)
pkg/client/models/operations/v2getschema.go (1)
  • V2GetSchemaRequest (9-14)
pkg/client/models/components/v2schemaresponse.go (1)
  • V2SchemaResponse (5-8)
internal/storage/ledger/store.go (3)
internal/controller/ledger/store.go (1)
  • Store (29-66)
internal/storage/common/resource.go (2)
  • PaginatedResource (447-450)
  • NewPaginatedResourceRepository (354-364)
internal/schema.go (1)
  • Schema (11-17)
test/e2e/api_transactions_list_test.go (2)
pkg/client/models/operations/v2listschemas.go (1)
  • Order (37-37)
pkg/client/models/operations/v2listtransactions.go (1)
  • QueryParamOrderEffective (17-17)
🪛 markdownlint-cli2 (0.18.1)
pkg/client/docs/models/operations/v2listtransactionsrequest.md

13-13: Spaces inside emphasis markers

(MD037, no-space-in-emphasis)


13-13: Spaces inside emphasis markers

(MD037, no-space-in-emphasis)

pkg/client/docs/sdks/v2/README.md

247-247: Hard tabs
Column: 1

(MD010, no-hard-tabs)


248-248: Hard tabs
Column: 1

(MD010, no-hard-tabs)


249-249: Hard tabs
Column: 1

(MD010, no-hard-tabs)


250-250: Hard tabs
Column: 1

(MD010, no-hard-tabs)


251-251: Hard tabs
Column: 1

(MD010, no-hard-tabs)


252-252: Hard tabs
Column: 1

(MD010, no-hard-tabs)


308-308: Hard tabs
Column: 1

(MD010, no-hard-tabs)


309-309: Hard tabs
Column: 1

(MD010, no-hard-tabs)


310-310: Hard tabs
Column: 1

(MD010, no-hard-tabs)


311-311: Hard tabs
Column: 1

(MD010, no-hard-tabs)


312-312: Hard tabs
Column: 1

(MD010, no-hard-tabs)


313-313: Hard tabs
Column: 1

(MD010, no-hard-tabs)


368-368: Hard tabs
Column: 1

(MD010, no-hard-tabs)


369-369: Hard tabs
Column: 1

(MD010, no-hard-tabs)


370-370: Hard tabs
Column: 1

(MD010, no-hard-tabs)


371-371: Hard tabs
Column: 1

(MD010, no-hard-tabs)


372-372: Hard tabs
Column: 1

(MD010, no-hard-tabs)


373-373: Hard tabs
Column: 1

(MD010, no-hard-tabs)

🔇 Additional comments (49)
pkg/client/README.md (1)

133-135: LGTM!

The documentation additions for the new schema operations are clear, well-formatted, and consistent with the existing API documentation structure.

internal/controller/ledger/listener.go (1)

16-16: LGTM!

The UpdatedSchema method addition is consistent with the existing Listener interface pattern, and the parameter naming is now aligned with other methods (using ledger instead of name).

pkg/client/docs/models/components/v2schemascursor.md (1)

1-11: LGTM!

The documentation for the V2SchemasCursor model is clear and follows the established format for cursor documentation. The field definitions are appropriate for a paginated response structure.

test/e2e/api_transactions_list_test.go (1)

275-275: LGTM!

The update from Order to QueryParamOrderEffective correctly reflects the repository-wide migration to the new QueryParamOrder enum type for v2 list operations.

pkg/client/models/components/v2schema.go (1)

1-50: LGTM!

This is Speakeasy-generated code that properly defines the V2Schema model with appropriate JSON serialization methods and nil-safe accessor methods. The implementation follows the established patterns for generated client models.

internal/bus/listener.go (1)

28-34: LGTM!

The UpdatedSchema method implementation is consistent with other listener methods, properly constructing and publishing the schema update event through the established event bus flow.

test/e2e/api_schema_test.go (1)

1-144: LGTM!

This comprehensive e2e test provides excellent coverage of the schema API functionality, including:

  • Empty state handling
  • Schema insertion and retrieval
  • Multiple version management
  • Error scenarios (non-existent and duplicate schemas)

The test structure follows established patterns and uses appropriate assertions.

internal/controller/ledger/listener_generated_test.go (1)

91-101: LGTM!

The generated mock methods for UpdatedSchema correctly follow the established gomock pattern and are consistent with other mock methods in this file.

internal/storage/ledger/store.go (1)

85-91: LGTM! Clean implementation following established patterns.

The Schemas() method follows the same resource repository pattern used by other methods in this store, with appropriate defaults (paginate by created_at descending).

internal/api/v2/controllers_schema_read.go (1)

12-28: LGTM! Clean handler with appropriate error handling.

The readSchema handler follows the established v2 controller pattern with proper error handling for not-found and general errors.

internal/controller/ledger/controller_with_events.go (1)

154-166: LGTM! Consistent event emission pattern.

The UpdateSchema method follows the established pattern for event-emitting wrappers, correctly using handleEvent for transaction-aware event scheduling and only emitting when not in dry-run mode.

pkg/client/models/components/v2schemascursorresponse.go (1)

5-14: LGTM! Standard cursor response wrapper.

The generated V2SchemasCursorResponse type follows the established pattern for wrapping cursor objects in API responses with nil-safe getters.

internal/api/v2/controllers_schema_read_test.go (1)

20-101: LGTM! Comprehensive test coverage.

The test suite covers all critical paths (nominal case, not found, backend errors) with proper mock setup and validation of both status codes and response payloads. Parallel execution is appropriately enabled.

pkg/client/models/components/v2schemascursor.go (1)

5-38: LGTM! Standard pagination cursor implementation.

The generated V2SchemasCursor type follows the established cursor pagination pattern with appropriate fields and nil-safe getters.

internal/storage/ledger/resource_schemas.go (1)

11-53: LGTM! Clean resource handler implementation.

The schemasResourceHandler follows the established pattern for repository handlers with:

  • Appropriate schema definition with paginated string and date fields
  • Standard BuildDataset with ledger filtering and PIT support
  • Proper filter resolution for supported properties
  • Clear rejection of unsupported expand operations
pkg/client/models/components/v2schemaresponse.go (1)

1-15: LGTM!

The V2SchemaResponse type and its nil-safe GetData() accessor follow standard patterns for generated SDK code.

internal/controller/ledger/store.go (1)

49-51: LGTM!

The new schema-related methods (InsertSchema, FindSchema, FindSchemas) are correctly added to the Store interface with appropriate signatures that align with the controller layer additions.

internal/api/v2/controllers_schema_list.go (1)

12-48: LGTM!

The listSchemas handler implementation follows the established v2 API patterns:

  • Appropriate default values (sort by "created_at" desc)
  • Standard error handling with BadRequest and HandleCommonPaginationErrors
  • Correct cursor rendering with schema transformation
internal/README.md (1)

110-112: LGTM!

The generated documentation correctly reflects the new public schema-related types (Schema, SchemaData, UpdatedSchema) and their associated constructors and methods.

Also applies to: 136-137, 186-218

internal/api/v2/controllers_schema_insert_test.go (1)

16-103: LGTM!

The test suite for insertSchema provides good coverage:

  • Nominal success case
  • Empty schema data handling
  • Invalid JSON validation
  • Backend error propagation

The test structure follows Go best practices with parallel execution and appropriate use of gomock and testify.

internal/controller/ledger/controller_with_traces.go (1)

496-538: LGTM!

The GetSchema and ListSchemas tracing wrappers are correctly implemented with appropriate span names that match their method names and proper histogram instrumentation.

internal/controller/ledger/controller.go (1)

82-87: LGTM!

The new Controller interface methods (UpdateSchema, GetSchema, ListSchemas) and the UpdateSchema parameter struct are well-defined with clear documentation and appropriate signatures that align with existing patterns in the codebase.

Also applies to: 134-137

internal/storage/ledger/schema.go (2)

22-35: Find by version scoped to ledger looks good

Correct table prefixing and ledger scoping; errors normalized.


37-39: Pagination hook is correct

Delegating to Schemas().Paginate aligns with existing resources.

internal/controller/ledger/controller_default.go (4)

63-66: UpdateSchema wiring via log processor looks correct

Use of forgeLog matches existing patterns.


78-84: Get/List schema methods look good

Thin wrappers over store; correct types and pagination usage.


163-164: Log processor initialization is consistent

Adds updateSchemaLp like other processors.


278-282: Import UpdatedSchema path is fine

Inserting payload.Schema before persisting the log aligns with other payloads. With the store fix, CreatedAt from payload or DB will persist correctly.

internal/controller/ledger/controller_with_too_many_client_handling.go (3)

125-137: Retry wrapper for UpdateSchema is consistent

Matches existing handleRetry pattern and returns all values.


139-151: Retry wrapper for GetSchema is consistent

Simple pass-through with retries.


152-163: Retry wrapper for ListSchemas is consistent

Correct pagination cursor type and retry handling.

pkg/client/v2.go (3)

656-849: V2 InsertSchema method matches SDK patterns

URL, body serialization (V2SchemaData), retries, hooks, and 204 handling are consistent.

Please ensure server returns 204 on success as coded here.


851-1057: V2 GetSchema deserialization path looks correct

Correct route, scopes, and JSON decode into V2SchemaResponse on 200.


1059-1269: V2 ListSchemas implementation is correct

Query params populated; 200 decodes to V2SchemasCursorResponse.

internal/api/v2/mocks_ledger_controller_test.go (3)

214-227: LGTM: GetSchema mock matches controller signature

Method and recorder wiring look correct: ctx + version -> (*ledger.Schema, error).


347-360: LGTM: ListSchemas mock returns typed cursor

Signature aligns with bunpaginate and ledger.Schema generics; recorder OK.


454-468: LGTM: UpdateSchema mock matches API contract Returns (*ledger.Log, *ledger.UpdatedSchema, error) and recorder wiring is correct; mocks and interface include GetSchema, ListSchemas, and UpdateSchema.

internal/api/bulking/mocks_ledger_controller_test.go (3)

214-227: LGTM: GetSchema mock (bulking) is consistent

Signature and recorder align with controller interface.


347-360: LGTM: ListSchemas mock (bulking) cursor typing OK

bunpaginate cursor over ledger.Schema; recorder matches.


454-468: LGTM: UpdateSchema mock (bulking) matches expected return types

Recorder method signature is correct.

internal/controller/ledger/store_generated_test.go (3)

152-166: LGTM: Store.FindSchema mock added correctly

ctx + version -> (*ledger.Schema, error); recorder matches.


167-181: LGTM: Store.FindSchemas mock returns typed cursor

Signature uses common.PaginatedQuery[any] and bunpaginate.Cursor[ledger.Schema]; consistent with other paginated resources.


226-239: Store.InsertSchema mock matches interface
Mock signature aligns with the Store interface’s InsertSchema(ctx context.Context, data *ledger.Schema) error; no regeneration needed.

internal/api/v1/mocks_ledger_controller_test.go (3)

214-227: LGTM: GetSchema mock (v1) added properly

Matches ctx + version -> (*ledger.Schema, error); recorder OK.


347-360: LGTM: ListSchemas mock (v1) returns cursor over ledger.Schema

Recorder and types are consistent with v2/bulking.


454-468: LGTM: UpdateSchema mock (v1) signature correct

Returns (*ledger.Log, *ledger.UpdatedSchema, error); recorder wiring good.

pkg/client/models/operations/v2listschemas.go (1)

69-74: Verify pagination defaults on server
controllers_schema_list.go handles sort (default created_at) and order (default desc); pagination uses bunpaginate.QueryDefaultPageSize. Ensure QueryDefaultPageSize equals the client’s default 15.

pkg/client/models/operations/v2insertschema.go (1)

17-47: LGTM!

The getter methods follow standard nil-safe patterns for generated code, correctly returning zero values when the receiver is nil.

internal/api/common/mocks_ledger_controller_test.go (1)

214-227: LGTM!

The three new mock methods (GetSchema, ListSchemas, UpdateSchema) are correctly generated and follow standard gomock patterns consistent with existing mocks in this file. The method signatures properly align with the controller interface definitions.

Also applies to: 347-360, 454-468

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (1)
pkg/client/docs/sdks/v2/README.md (1)

243-253: Replace hard tabs with spaces in Go examples

The import blocks in the InsertSchema, GetSchema, and ListSchemas examples still use hard tabs for indentation, which triggers MD010 (no-hard-tabs) from markdownlint and matches earlier review feedback.

Consider normalizing these blocks to use spaces like the rest of the README, for example:

-import(
-	"context"
-	"os"
-	"github.com/formancehq/ledger/pkg/client/models/components"
-	"github.com/formancehq/ledger/pkg/client"
-	"github.com/formancehq/ledger/pkg/client/models/operations"
-	"log"
-)
+import (
+    "context"
+    "os"
+    "github.com/formancehq/ledger/pkg/client/models/components"
+    "github.com/formancehq/ledger/pkg/client"
+    "github.com/formancehq/ledger/pkg/client/models/operations"
+    "log"
+)

Apply the same pattern to the corresponding import blocks in the GetSchema and ListSchemas sections.

Also applies to: 317-324, 377-384

🧹 Nitpick comments (18)
internal/api/v2/controllers_schema_read_test.go (3)

86-92: Use api.Decode for error responses for consistency

The success branch at line 97 already uses api.Decode(t, rec.Body, &response), but the error branch (lines 86-92) uses manual json.Unmarshal. All other v2 API test files follow the api.Decode pattern for error decoding. Update to api.Decode(t, rec.Body, &errorResponse) to standardize the approach.


10-10: Remove spew.Dump(response.Data) from test success path

Verification confirms: spew.Dump(response.Data) at line 96 executes before api.Decode on line 97, printing an uninitialized zero-valued struct. This adds test output noise without testing value. Assertions at lines 98–99 validate decoded data only. Safe to remove.

Location: internal/api/v2/controllers_schema_read_test.go:96


15-16: Add import of controller layer and use ledgercontroller.ErrNotFound alias for consistency

The test should import internal/controller/ledger and use the ledgercontroller.ErrNotFound alias instead of postgres.ErrNotFound at line 55. This aligns with the established pattern used by other similar tests in the same package (e.g., controllers_transactions_revert_test.go, controllers_transactions_delete_metadata_test.go) and better reflects the controller contract rather than exposing storage layer details.

internal/storage/ledger/schema_test.go (1)

14-29: Schema insert IT test is good; double‑check parallelism and empty chart semantics

This test correctly exercises NewSchema + InsertSchema and asserts Version and CreatedAt, which is useful coverage for the new schema path. Two things to confirm:

  • t.Parallel() on an integration test is safe with your newLedgerStore(t) setup (shared DB / migrations / unique ledger names), otherwise consider dropping parallelism to avoid flakiness.
  • Passing an empty (but non‑nil) Chart makes “no chart at all” invalid but “empty chart” valid. That contract is now codified by this test; confirm this matches the intended business rule.
internal/errors.go (1)

90-113: New schema/account error types are well‑structured; consider optional enhancements

ErrInvalidSchema and ErrInvalidAccount follow the same pattern as the existing error types and will work nicely with errors.Is. Two optional enhancements you might consider (not blocking):

  • Add an Unwrap() error method on ErrInvalidSchema if you want callers to introspect the underlying validation error.
  • For ErrInvalidAccount.Error, you may want to render path via strings.Join(e.path, ":") (or whatever delimiter you use for accounts) instead of %v on the slice, for a more user‑friendly message.

Otherwise, these additions look good.

internal/api/v2/views.go (2)

123-133: Account marshalling now properly reuses stringified volumes when big‑int‑as‑string is requested

Wrapping Volumes and EffectiveVolumes in volumesByAssets within account.MarshalJSON ensures that, when renderAccount returns account(v), nested volume fields honor the big‑int‑as‑string encoding. This is a nice consistency fix; no further changes needed.


144-146: renderSchema helper is fine; confirm whether schema ever needs big‑int handling

renderSchema currently just returns the ledger.Schema as‑is, ignoring the Formance-Bigint-As-String header. That’s perfectly reasonable if schema/chart structures don’t embed any *big.Int values that must be stringified. If they do (now or in the future), you may want a schema wrapper similar to transaction/account to keep behavior consistent across endpoints.

internal/controller/ledger/log_process_test.go (1)

34-37: Mocking FindSchemas aligns tests with new schema-aware log processing

The added FindSchemas expectations returning an empty bunpaginate.Cursor[ledger.Schema] correctly adapt these tests to the new schema lookup inside forgeLog, without altering the original behaviors being asserted (IK conflict and deadlock retry). If you see more tests needing the same “no schemas” setup, consider a small helper to reduce duplication, but it’s not required here.

Also applies to: 69-72, 82-85

test/e2e/api_schema_test.go (1)

350-395: Tighten log-type matching in schemaVersion log test

The log-inspection test is solid and validates that SchemaVersion flows into V2Log. To make it a bit more robust and self-documenting, you might:

  • Compare log.Type against the corresponding components.V2LogType constant instead of the raw "NEW_TRANSACTION" string, so refactors of the enum won’t silently break this test.
  • (Optional) If V2ListLogs ever returns multiple NEW_TRANSACTION logs, consider also checking some aspect of the data (e.g., reference, amount) to be sure you’re asserting against the transaction created in this spec.

Behavior is fine as-is; these are just resilience/readability tweaks.

internal/api/v2/controllers_schema_insert_test.go (2)

30-81: Consider adding test case for schema already exists error.

The test cases provide good coverage of common scenarios. However, based on the new ErrSchemaAlreadyExists constant added in internal/api/common/errors.go, consider adding a test case that verifies the endpoint properly handles attempts to insert a duplicate schema version.

Example test case to add:

{
	name:    "schema already exists",
	version: "v1.0.0",
	requestBody: map[string]interface{}{
		"rules": []map[string]interface{}{},
	},
	expectStatusCode:  http.StatusBadRequest,
	expectedErrorCode: common.ErrSchemaAlreadyExists,
	expectBackendCall: true,
	returnErr:         ledgercontroller.ErrSchemaAlreadyExists{},
},

97-97: Consider handling JSON marshaling errors in test setup.

The test silently ignores potential JSON marshaling errors. While test data is controlled and unlikely to fail marshaling, explicitly handling this error would make test failures clearer if the test data is modified incorrectly in the future.

-body, _ := json.Marshal(tc.requestBody)
+body, err := json.Marshal(tc.requestBody)
+require.NoError(t, err)
internal/chart_test.go (1)

288-404: Name the failing posting test cases for clearer diagnostics

Both failing cases in TestPostingValidation use an empty name field, which makes it harder to see which scenario failed when the test breaks. Consider giving them descriptive names (e.g. "invalid source" / "invalid destination").

internal/controller/ledger/controller_default_test.go (1)

26-61: CreateTransaction tests exercise both schema and no-schema paths; minor simplification possible

The testCreateTransaction helper plus TestCreateTransaction give good coverage of both flows (no schemas vs. explicit SchemaVersion with a minimal chart), and the FindSchema/FindSchemas expectations line up with the new log processing logic.

Two optional tweaks you might consider:

  • The explicit InsertSchema expectation and call on the mock store aren’t needed for CreateTransaction itself, since the controller only calls FindSchema/FindSchemas. Removing that setup would simplify the test without changing its behavior.
  • For completeness, you could add a dedicated test where the ledger already has at least one schema but SchemaVersion is left empty, asserting that the controller surfaces the expected ErrSchemaNotSpecified path.

Both are non-blocking; the existing tests already validate the main happy paths.

Also applies to: 84-93, 107-120

internal/controller/ledger/errors.go (1)

249-320: Schema error types fit existing patterns; consider adding Unwrap for validation error

The new schema-related errors (not-found, validation, not-specified, already-exists) follow the same value-type + Is pattern as the rest of this file and give you clear error classes to surface up the stack.

One small improvement you might consider: add an Unwrap() error method to ErrSchemaValidationError so callers can still inspect the underlying validation error via errors.Is/errors.As when needed, without changing how the typed wrapper is matched today.

internal/controller/ledger/controller_default.go (1)

222-275: Import now correctly handles UpdatedSchema logs but treats duplicate schemas as hard failures

The new ledger.UpdatedSchema case inserts payload.Schema via InsertSchema, which is consistent with the live UpdateSchema path. On import into a non‑empty database, a duplicate schema version will cause InsertSchema to fail and abort the import. Given the import precondition (logs must strictly append after the last existing log), this is acceptable, but it does mean importing a history that contains schema updates into a ledger that already holds those schema rows will fail even if log IDs don’t overlap.

If you ever need import to be idempotent with respect to schema rows (e.g., re-importing a full history into a fresh-but-preseeded DB), consider optionally special‑casing ErrConstraintsFailed here to either ignore duplicates or surface a more explicit import error.

Also applies to: 277-287

internal/log.go (2)

20-26: UpdatedSchemaLogType is wired correctly; panics on unknown types remain

UpdatedSchemaLogType is added to the enum, String(), and LogTypeFromString(), so serialization and deserialization of "UPDATED_SCHEMA" are consistent. Both functions still panic("invalid log type") for unknown values; that matches existing behavior but leaves no graceful recovery path if the DB or input is corrupted.

If you want to harden against bad data, consider returning an explicit error instead of panicking when mapping unknown log types.

Also applies to: 54-69, 71-86


267-331: Metadata and revert payloads mark that they “need” a schema but currently perform no validation

SavedMetadata, DeletedMetadata, and RevertedTransaction all return NeedsSchema() == true while their ValidateWithSchema methods are no‑ops. This is fine as a staging step if the intent is just to require a schema version to be specified for these operations without enforcing additional rules yet, but it’s easy for future contributors to assume real validation is already happening.

Either (a) add a brief comment near these methods clarifying that they are intentionally no‑ops for now, or (b) flip NeedsSchema() to false until you actually enforce metadata/revert rules via the schema.

Also applies to: 371-383

internal/chart.go (1)

224-261: Account schema lookup recursion is correct but recompiles regexes per lookup

findAccountSchema cleanly walks fixed and variable segments, accumulates the visited path for ErrInvalidAccount, and honors the special world case in FindAccountSchema. The recursion always advances (account[1:]) when descending, so no infinite loop is possible.

You already validate the pattern during unmarshalling; to avoid recompiling it on every lookup, consider storing a precompiled *regexp.Regexp on ChartVariableSegment (unexported) and using it instead of regexp.Match here.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between b25c875 and 48305f2.

⛔ Files ignored due to path filters (7)
  • docs/events/UpdatedSchema.json is excluded by !**/*.json
  • go.mod is excluded by !**/*.mod
  • openapi.yaml is excluded by !**/*.yaml
  • openapi/v2.yaml is excluded by !**/*.yaml
  • pkg/client/.speakeasy/gen.lock is excluded by !**/*.lock, !**/*.lock
  • pkg/client/.speakeasy/logs/naming.log is excluded by !**/*.log, !**/*.log
  • pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log is excluded by !**/*.log, !**/*.log
📒 Files selected for processing (59)
  • cmd/docs_events.go (1 hunks)
  • docs/api/README.md (22 hunks)
  • internal/README.md (37 hunks)
  • internal/api/bulking/bulker.go (6 hunks)
  • internal/api/bulking/elements.go (1 hunks)
  • internal/api/common/errors.go (2 hunks)
  • internal/api/v2/controllers_schema_insert.go (1 hunks)
  • internal/api/v2/controllers_schema_insert_test.go (1 hunks)
  • internal/api/v2/controllers_schema_read_test.go (1 hunks)
  • internal/api/v2/controllers_transactions_create.go (1 hunks)
  • internal/api/v2/query.go (1 hunks)
  • internal/api/v2/views.go (9 hunks)
  • internal/chart.go (1 hunks)
  • internal/chart_test.go (1 hunks)
  • internal/controller/ledger/controller_default.go (4 hunks)
  • internal/controller/ledger/controller_default_test.go (7 hunks)
  • internal/controller/ledger/errors.go (1 hunks)
  • internal/controller/ledger/log_process.go (2 hunks)
  • internal/controller/ledger/log_process_test.go (4 hunks)
  • internal/controller/ledger/parameters.go (1 hunks)
  • internal/errors.go (1 hunks)
  • internal/log.go (13 hunks)
  • internal/schema.go (1 hunks)
  • internal/storage/bucket/default_bucket.go (1 hunks)
  • internal/storage/bucket/migrations/35-create-compute-hash-function/up.sql (1 hunks)
  • internal/storage/bucket/migrations/41-add-schema/up.sql (1 hunks)
  • internal/storage/ledger/schema_test.go (1 hunks)
  • pkg/client/docs/models/components/dotself.md (1 hunks)
  • pkg/client/docs/models/components/v2bulkelementaddmetadata.md (1 hunks)
  • pkg/client/docs/models/components/v2bulkelementcreatetransaction.md (1 hunks)
  • pkg/client/docs/models/components/v2bulkelementdeletemetadata.md (1 hunks)
  • pkg/client/docs/models/components/v2bulkelementreverttransaction.md (1 hunks)
  • pkg/client/docs/models/components/v2chartrules.md (1 hunks)
  • pkg/client/docs/models/components/v2chartsegment.md (1 hunks)
  • pkg/client/docs/models/components/v2errorsenum.md (1 hunks)
  • pkg/client/docs/models/components/v2log.md (1 hunks)
  • pkg/client/docs/models/components/v2logtype.md (1 hunks)
  • pkg/client/docs/models/components/v2schemadata.md (1 hunks)
  • pkg/client/docs/models/operations/v2addmetadataontransactionrequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2addmetadatatoaccountrequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2createbulkrequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2createtransactionrequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2reverttransactionrequest.md (1 hunks)
  • pkg/client/docs/sdks/v2/README.md (7 hunks)
  • pkg/client/models/components/v2bulkelementaddmetadata.go (2 hunks)
  • pkg/client/models/components/v2bulkelementcreatetransaction.go (2 hunks)
  • pkg/client/models/components/v2bulkelementdeletemetadata.go (2 hunks)
  • pkg/client/models/components/v2bulkelementreverttransaction.go (2 hunks)
  • pkg/client/models/components/v2chartrules.go (1 hunks)
  • pkg/client/models/components/v2chartsegment.go (1 hunks)
  • pkg/client/models/components/v2errorsenum.go (2 hunks)
  • pkg/client/models/components/v2log.go (4 hunks)
  • pkg/client/models/components/v2schemadata.go (1 hunks)
  • pkg/client/models/operations/v2addmetadataontransaction.go (2 hunks)
  • pkg/client/models/operations/v2addmetadatatoaccount.go (2 hunks)
  • pkg/client/models/operations/v2createbulk.go (2 hunks)
  • pkg/client/models/operations/v2createtransaction.go (2 hunks)
  • pkg/client/models/operations/v2reverttransaction.go (2 hunks)
  • test/e2e/api_schema_test.go (1 hunks)
✅ Files skipped from review due to trivial changes (3)
  • pkg/client/docs/models/components/dotself.md
  • pkg/client/docs/models/components/v2chartsegment.md
  • pkg/client/docs/models/components/v2chartrules.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • internal/api/v2/controllers_schema_insert.go
  • internal/schema.go
  • pkg/client/docs/models/components/v2schemadata.md
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-04-29T11:24:28.923Z
Learnt from: gfyrag
Repo: formancehq/ledger PR: 892
File: internal/controller/ledger/controller_default.go:196-196
Timestamp: 2025-04-29T11:24:28.923Z
Learning: In the ledger Import function, it's critical to maintain proper log ID tracking by updating lastLogID with the current log.ID after each processed log, rather than setting it to nil. This ensures the system can properly validate the ordering of logs and prevent duplicate or out-of-order processing, which is essential for maintaining data integrity in the ledger.

Applied to files:

  • internal/README.md
  • internal/controller/ledger/controller_default.go
  • internal/controller/ledger/log_process.go
🧬 Code graph analysis (20)
internal/api/common/errors.go (1)
internal/controller/ledger/errors.go (5)
  • ErrInvalidIdempotencyInput (221-225)
  • ErrSchemaValidationError (268-271)
  • ErrNotFound (14-14)
  • ErrSchemaNotSpecified (289-289)
  • ErrSchemaNotFound (249-251)
pkg/client/models/operations/v2reverttransaction.go (1)
pkg/client/models/components/v2reverttransactionrequest.go (1)
  • V2RevertTransactionRequest (5-7)
cmd/docs_events.go (1)
internal/log.go (1)
  • UpdatedSchema (418-420)
pkg/client/models/operations/v2createbulk.go (1)
pkg/client/models/components/v2bulkelement.go (1)
  • V2BulkElement (21-28)
internal/api/v2/controllers_schema_insert_test.go (3)
internal/controller/ledger/errors.go (1)
  • ErrSchemaValidationError (268-271)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (134-137)
internal/api/v2/routes.go (1)
  • NewRouter (21-123)
internal/chart_test.go (2)
internal/chart.go (5)
  • ChartOfAccounts (36-36)
  • ChartVariableSegment (23-28)
  • ChartSegment (17-21)
  • ChartAccount (12-15)
  • ChartAccountRules (10-10)
internal/errors.go (1)
  • ErrInvalidAccount (102-105)
test/e2e/api_schema_test.go (16)
test/e2e/suite_test.go (1)
  • UseTemplatedDatabase (143-145)
pkg/client/models/operations/v2createledger.go (1)
  • V2CreateLedgerRequest (9-13)
pkg/client/models/operations/v2listschemas.go (1)
  • V2ListSchemasRequest (63-74)
pkg/client/models/components/v2schemascursorresponse.go (1)
  • V2SchemasCursorResponse (5-7)
pkg/client/models/operations/v2insertschema.go (1)
  • V2InsertSchemaRequest (9-15)
pkg/client/models/components/v2schemadata.go (1)
  • V2SchemaData (6-9)
pkg/client/models/components/v2chartsegment.go (1)
  • V2ChartSegment (13-19)
pkg/client/models/components/v2chartrules.go (1)
  • V2ChartRules (5-6)
pkg/client/models/components/v2errorsenum.go (4)
  • V2ErrorsEnumSchemaAlreadyExists (30-30)
  • V2ErrorsEnumValidation (15-15)
  • V2ErrorsEnumSchemaNotSpecified (31-31)
  • V2ErrorsEnumNotFound (19-19)
pkg/client/models/operations/v2getschema.go (1)
  • V2GetSchemaRequest (9-14)
pkg/client/models/components/v2schemaresponse.go (1)
  • V2SchemaResponse (5-8)
pkg/client/models/operations/v2createtransaction.go (1)
  • V2CreateTransactionRequest (9-27)
pkg/client/models/components/v2posttransaction.go (1)
  • V2PostTransaction (58-68)
pkg/client/models/components/v2posting.go (1)
  • V2Posting (10-15)
pkg/client/models/operations/v2listlogs.go (1)
  • V2ListLogsRequest (11-29)
pkg/client/models/components/v2log.go (1)
  • V2Log (48-56)
pkg/client/models/components/v2schemadata.go (1)
pkg/client/models/components/v2chartsegment.go (1)
  • V2ChartSegment (13-19)
internal/api/v2/controllers_schema_read_test.go (3)
internal/schema.go (2)
  • Schema (14-20)
  • SchemaData (10-12)
internal/controller/ledger/errors.go (1)
  • ErrNotFound (14-14)
internal/api/v2/routes.go (1)
  • NewRouter (21-123)
internal/log.go (5)
internal/storage/ledger/logs.go (1)
  • Log (19-25)
internal/schema.go (1)
  • Schema (14-20)
internal/transaction.go (2)
  • Transaction (36-50)
  • Transaction (52-59)
internal/posting.go (1)
  • Postings (35-35)
pkg/events/message.go (4)
  • SavedMetadata (26-31)
  • DeletedMetadata (59-64)
  • RevertedTransaction (43-47)
  • UpdatedSchema (76-79)
internal/storage/ledger/schema_test.go (2)
internal/schema.go (2)
  • NewSchema (22-32)
  • SchemaData (10-12)
internal/chart.go (1)
  • ChartSegment (17-21)
internal/controller/ledger/log_process_test.go (1)
internal/schema.go (1)
  • Schema (14-20)
internal/controller/ledger/errors.go (1)
internal/api/common/errors.go (2)
  • ErrSchemaNotSpecified (27-27)
  • ErrSchemaAlreadyExists (26-26)
internal/controller/ledger/controller_default.go (5)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (134-137)
internal/log.go (2)
  • UpdatedSchema (418-420)
  • Log (89-102)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/controller/ledger/store.go (1)
  • Store (29-66)
internal/schema.go (2)
  • NewSchema (22-32)
  • Schema (14-20)
internal/chart.go (1)
internal/errors.go (1)
  • ErrInvalidAccount (102-105)
pkg/client/models/components/v2chartsegment.go (2)
pkg/client/models/components/v2chartrules.go (1)
  • V2ChartRules (5-6)
pkg/client/internal/utils/json.go (2)
  • MarshalJSON (21-115)
  • UnmarshalJSON (117-251)
internal/controller/ledger/log_process.go (4)
internal/schema.go (1)
  • Schema (14-20)
internal/controller/ledger/errors.go (1)
  • ErrNotFound (14-14)
internal/storage/common/pagination.go (1)
  • InitialPaginatedQuery (9-14)
internal/log.go (2)
  • NewLog (194-199)
  • ComputeIdempotencyHash (459-468)
pkg/client/models/components/v2bulkelementcreatetransaction.go (1)
pkg/client/models/components/v2posttransaction.go (1)
  • V2PostTransaction (58-68)
internal/controller/ledger/controller_default_test.go (4)
internal/schema.go (2)
  • Schema (14-20)
  • SchemaData (10-12)
internal/chart.go (1)
  • ChartOfAccounts (36-36)
internal/controller/ledger/controller.go (2)
  • CreateTransaction (101-105)
  • RunScript (90-90)
internal/machine/vm/run.go (1)
  • RunScript (14-19)
internal/api/v2/views.go (3)
internal/transaction.go (2)
  • Transaction (36-50)
  • Transaction (52-59)
internal/volumes.go (3)
  • PostCommitVolumes (118-118)
  • Volumes (12-15)
  • Volumes (40-43)
internal/schema.go (1)
  • Schema (14-20)
🪛 markdownlint-cli2 (0.18.1)
pkg/client/docs/sdks/v2/README.md

247-247: Hard tabs
Column: 1

(MD010, no-hard-tabs)


248-248: Hard tabs
Column: 1

(MD010, no-hard-tabs)


249-249: Hard tabs
Column: 1

(MD010, no-hard-tabs)


250-250: Hard tabs
Column: 1

(MD010, no-hard-tabs)


251-251: Hard tabs
Column: 1

(MD010, no-hard-tabs)


252-252: Hard tabs
Column: 1

(MD010, no-hard-tabs)


318-318: Hard tabs
Column: 1

(MD010, no-hard-tabs)


319-319: Hard tabs
Column: 1

(MD010, no-hard-tabs)


320-320: Hard tabs
Column: 1

(MD010, no-hard-tabs)


321-321: Hard tabs
Column: 1

(MD010, no-hard-tabs)


322-322: Hard tabs
Column: 1

(MD010, no-hard-tabs)


323-323: Hard tabs
Column: 1

(MD010, no-hard-tabs)


378-378: Hard tabs
Column: 1

(MD010, no-hard-tabs)


379-379: Hard tabs
Column: 1

(MD010, no-hard-tabs)


380-380: Hard tabs
Column: 1

(MD010, no-hard-tabs)


381-381: Hard tabs
Column: 1

(MD010, no-hard-tabs)


382-382: Hard tabs
Column: 1

(MD010, no-hard-tabs)


383-383: Hard tabs
Column: 1

(MD010, no-hard-tabs)

🔇 Additional comments (72)
pkg/client/docs/models/operations/v2addmetadatatoaccountrequest.md (1)

12-12: ✓ Schema field addition is properly documented.

The new SchemaVersion field is appropriately positioned in the request model documentation, clearly described, and consistent with the schema support feature being introduced across the PR. The optional marker and example value are suitable.

pkg/client/models/operations/v2createtransaction.go (2)

20-21: SchemaVersion query param wiring looks consistent

Adding SchemaVersion *string with the schemaVersion query tag aligns with the existing style for optional query parameters (e.g., DryRun, Force) and keeps the API surface coherent. No issues from this file’s perspective.


57-62: Getter implementation matches existing pattern

GetSchemaVersion mirrors the other getters’ nil-safety pattern and correctly exposes the new field without changing behavior when the receiver is nil.

internal/api/v2/controllers_transactions_create.go (1)

4-5: LGTM! Import cleanup.

The import block reorganization correctly positions the net/http import, which is used by the function signature on line 18.

pkg/client/models/operations/v2createbulk.go (2)

51-56: LGTM: Accessor method correctly implemented.

The GetSchemaVersion method follows the same nil-safety pattern as other accessor methods in this generated file.


17-20: LGTM: Generated code follows consistent patterns and supports per-element schema version overrides.

The SchemaVersion field is correctly implemented as an optional query parameter for the operation. Verification confirms that individual bulk element types (V2BulkElementRevertTransaction, V2BulkElementDeleteMetadata, V2BulkElementAddMetadata, V2BulkElementCreateTransaction) all have their own SchemaVersion fields, supporting the per-element override capability mentioned in the code comment.

pkg/client/models/components/v2chartrules.go (1)

5-6: Empty struct serves as a placeholder for future rules.

The empty V2ChartRules struct is appropriate as a type marker and allows for future extension without breaking the API.

pkg/client/models/components/v2schemadata.go (2)

5-9: Previous empty struct concern has been resolved.

The Chart field is now present, addressing the earlier feedback about empty JSON payloads. The structure properly models the schema payload.


11-16: Nil-safe accessor with sensible default.

The GetChart method correctly handles nil receivers by returning an empty map, preventing potential nil pointer dereferences.

pkg/client/models/components/v2chartsegment.go (4)

9-10: Empty marker type for self-reference semantics.

Like V2ChartRules, DotSelf serves as a type marker with the special .self JSON key, enabling future extension.


12-19: Recursive structure enables nested chart segments.

The AdditionalProperties field uses a recursive type to support hierarchical chart structures. The dot-prefixed JSON keys (.self, .pattern, etc.) distinguish reserved fields from dynamic properties, which is appropriate for this schema model.


21-30: Custom marshaling correctly handles additional properties.

The JSON marshaling delegates to utility functions that properly serialize the AdditionalProperties field alongside the named fields, as confirmed by the utils package implementation.


32-65: Consistent nil-safe getters for all fields.

All getter methods properly handle nil receivers, maintaining API safety across the public interface.

pkg/client/models/operations/v2addmetadataontransaction.go (2)

20-21: LGTM: SchemaVersion field properly defined.

The field follows the same pattern as other optional query parameters in the struct, with appropriate type, tag, and documentation.


65-70: Based on the verification, the GetSchemaVersion() accessor method is correctly implemented and follows the established pattern used by other optional query parameters in the struct. The implementation properly handles nil receivers and returns the field value.

Regarding the original review comment's request to validate SchemaVersion usage: In Go SDKs, optional parameters are typically represented as pointers, and a nil value indicates the parameter was not provided. The client-side code correctly exposes SchemaVersion as an optional parameter through this accessor. Validation of query parameters is typically handled on the backend API layer, not in the client library.

The test coverage shows that SchemaVersion is intentionally never passed, confirming it functions as a truly optional parameter that has no required validation at the client level.

internal/api/v2/controllers_schema_read_test.go (1)

21-32: Good, focused table-driven coverage of read-schema behavior

The table tests cover the main paths (OK, not-found, generic backend error), use t.Parallel safely with per-subtest setup, and assert both HTTP status and payload shape. The GetSchema mock expectations also align correctly with the router path semantics.

Also applies to: 41-65, 67-102

pkg/client/docs/models/operations/v2createbulkrequest.md (1)

12-12: SchemaVersion field addition is correctly implemented and consistently applied across operations.

Verification confirms that the SchemaVersion field has been properly added to five related V2 operations:

  • v2createbulkrequest.md (with context-appropriate description: "Default schema version...can be overridden per element")
  • v2createtransactionrequest.md
  • v2addmetadataontransactionrequest.md
  • v2reverttransactionrequest.md
  • v2addmetadatatoaccountrequest.md

The markdown descriptions match their corresponding Go struct comments exactly, and the field type, optional marking, and example values are consistent across all operations. No stale or incomplete schema documentation was found.

The exact word-for-word correspondence between Go code comments and markdown documentation confirms the docs are auto-generated from source code (likely by external CI tooling). This ensures consistency between implementation and documentation.

internal/api/bulking/elements.go (1)

23-28: BulkElement.SchemaVersion addition looks correct and backward compatible

Adding SchemaVersion string \json:"schemaVersion,omitempty"`onBulkElementcleanly surfaces schema version in bulk payloads without breaking existing clients (field is optional). No changes are needed here; just ensure downstream bulk processing actually reads and propagatesSchemaVersion` where required.

cmd/docs_events.go (1)

32-38: UpdatedSchema inclusion in docs generation is consistent

Adding events.UpdatedSchema{} to the loop aligns with the existing pattern and will generate the JSON schema alongside other event payloads. No issues here; just remember to run the docs generation step so the new schema file is committed where expected.

pkg/client/docs/models/operations/v2addmetadataontransactionrequest.md (1)

6-13: SchemaVersion field doc is consistent with the new API surface

The added SchemaVersion row (optional string, “Schema version to use for validation”, example v1.0.0) matches the intent of the rest of the PR. Just ensure the underlying client model actually exposes a SchemaVersion field with the same casing and JSON name so docs and code stay aligned.

pkg/client/models/components/v2errorsenum.go (1)

12-33: New schema‑related error codes are wired correctly into V2ErrorsEnum

The additions of V2ErrorsEnumSchemaAlreadyExists and V2ErrorsEnumSchemaNotSpecified plus their handling in UnmarshalJSON are consistent with the existing pattern and won’t affect other variants. No further changes needed here.

Also applies to: 38-83

internal/api/v2/views.go (7)

16-25: volumes JSON marshalling remains safe; any field types are mostly cosmetic

The updated volumes.MarshalJSON still always emits strings for input, output, and balance, and the change to any-typed fields doesn’t alter behavior. Given this is only used through the big‑int‑as‑string path, there’s no functional concern here.


46-53: Posting wrapper continues to stringify Amount correctly

posting.MarshalJSON still embeds ledger.Posting and exposes Amount as p.Amount.String(), which is consistent with the existing big‑int handling. No issues from this change.


98-111: volumesWithBalanceByAssetByAccount marshalling aligns with big‑int‑as‑string behavior

Serializing Input, Output, and Balance via .String() is consistent with the big‑int‑as‑string semantics used elsewhere. As long as these fields are always non‑nil in practice (same assumption as before), this is fine.


166-175: createdTransaction wrapper correctly reuses transaction marshaller

Using:

type createdTransaction ledger.CreatedTransaction

func (tx createdTransaction) MarshalJSON() ([]byte, error) {
    type Aux ledger.CreatedTransaction
    return json.Marshal(struct {
        Aux
        Transaction transaction `json:"transaction"`
    }{
        Aux:         Aux(tx),
        Transaction: transaction(tx.Transaction),
    })
}

ensures that nested Transaction fields in created‑transaction logs benefit from the custom transaction.MarshalJSON logic (including big‑int‑as‑string behavior). This looks correct.


179-189: revertedTransaction marshalling mirrors createdTransaction and looks correct

The revertedTransaction wrapper delegates both RevertedTransaction and RevertTransaction fields to the transaction wrapper, which keeps their serialization consistent with other transaction payloads when big‑int‑as‑string is enabled. No issues here.


194-212: Log marshalling correctly wraps transaction‑carrying log entries

The updated log.MarshalJSON:

  • Embeds ledger.Log via Aux.
  • Wraps l.Data in createdTransaction or revertedTransaction when l.Type is NewTransactionLogType or RevertedTransactionLogType, respectively.
  • Leaves other log types’ Data unchanged.

This is the right place to inject big‑int‑aware marshalling for nested transactions. Just ensure that for those log types l.Data is always of the expected concrete type; otherwise, the type assertions would panic.


60-85: Verification confirms implementation is correct—no mutation occurs

The SubtractPostings method is properly implemented as non-mutating. It creates a deep copy via a.Copy() before performing any modifications, then returns only the copy. The receiver's underlying maps remain untouched, making the code safe for repeated marshalling and tx reuse.

internal/storage/bucket/migrations/41-add-schema/up.sql (1)

1-18: I'll verify the core concern about ALTER TYPE inside a transaction block. Let me search the codebase and check PostgreSQL constraints.

Now let me check how this codebase handles migrations and whether similar patterns exist:

Based on my verification, if ALTER TYPE ... ADD VALUE is executed inside a transaction block, the new value cannot be used until after the transaction has been committed. However, this is allowed in modern PostgreSQL — it's not blocked outright as the original review suggests by referencing older documentation.

The review raises a concern about transaction wrapping based on the codebase using PostgreSQL 16-alpine, where this operation is supported inside transaction blocks. While your specific migration runner's transaction behavior matters for full certainty, the PostgreSQL version used (16) does permit this pattern within transactions.

The original concern conflates two distinct constraints: (1) PostgreSQL refusing the operation entirely (pre-9.1 behavior) versus (2) the value not being usable within the same transaction (current behavior in PostgreSQL 11+). The code as written will execute successfully on PostgreSQL 16.

To finalize this, inspect how your migration runner wraps DO $$ … $$ blocks in transactions.

pkg/client/docs/sdks/v2/README.md (2)

11-13: New schema operations list looks consistent

The added InsertSchema, GetSchema, and ListSchemas entries correctly surface the new V2 schema endpoints alongside the existing ledger operations; no issues from a docs perspective.


641-642: SchemaVersion examples are coherent across operations

The added SchemaVersion: client.String("v1.0.0") fields in the CreateBulk, AddMetadataToAccount, CreateTransaction, AddMetadataOnTransaction, and RevertTransaction examples are consistent with the new schema-versioning docs and naming.

Also applies to: 898-899, 1211-1212, 1373-1374, 1501-1502

pkg/client/docs/models/components/v2logtype.md (1)

8-12: UPDATED_SCHEMA log type documentation looks correct

Adding V2LogTypeUpdatedSchemaUPDATED_SCHEMA aligns the docs with the new log type introduced in the logging system; nothing further needed here.

pkg/client/docs/models/components/v2errorsenum.md (1)

24-27: New schema-related error enums are consistent

The SCHEMA_ALREADY_EXISTS and SCHEMA_NOT_SPECIFIED entries look consistent with the surrounding naming and error taxonomy, and match the new schema error cases described for the API.

pkg/client/docs/models/operations/v2createtransactionrequest.md (1)

11-13: SchemaVersion field addition and Force deprecation are clearly documented

The new SchemaVersion field (with example v1.0.0) is documented consistently with the rest of the schema-versioning changes, and the Force field is clearly marked as deprecated, which should help clients migrate.

pkg/client/docs/models/components/v2bulkelementcreatetransaction.md (1)

6-11: Bulk create-transaction element SchemaVersion docs are aligned

The added SchemaVersion field (optional string with example v1.0.0) and the extended table structure are consistent with other bulk element and request docs for schema validation.

pkg/client/docs/models/components/v2bulkelementdeletemetadata.md (1)

6-11: Bulk delete-metadata element SchemaVersion docs are consistent

The new SchemaVersion field and Example column follow the same pattern as other bulk elements and correctly describe schema-based validation.

internal/storage/bucket/migrations/35-create-compute-hash-function/up.sql (1)

16-21: Hash now correctly incorporates schema_version while preserving old behavior

Conditionally appending "schemaVersion" when r.schema_version is not null, and moving the final } to after that conditional, keeps the marshalled JSON identical for existing rows (no schema version) while ensuring the schema version participates in the hash when present.

internal/api/v2/query.go (1)

11-17: SchemaVersion query parameter wiring is straightforward and safe

Populating SchemaVersion from r.URL.Query().Get("schemaVersion") in getCommandParameters cleanly exposes the new query parameter to downstream ledger logic, and defaults to "" when omitted without changing existing behavior.

internal/controller/ledger/parameters.go (1)

3-7: SchemaVersion field wiring looks good; confirm empty-string semantics

Adding SchemaVersion string to Parameters[INPUT] cleanly supports schema propagation from the API layer. Since it’s a plain string (not a pointer), please confirm that all callers/consumers consistently treat "" as “no schema version specified” and don’t rely on distinguishing unset vs explicitly empty.

pkg/client/docs/models/components/v2bulkelementaddmetadata.md (1)

6-11: Docs for SchemaVersion on V2BulkElementAddMetadata are consistent

The added SchemaVersion field (optional string with validation-focused description and example) matches the corresponding Go model and other schemaVersion docs in this PR. No issues from a docs/API consistency standpoint.

pkg/client/docs/models/operations/v2reverttransactionrequest.md (1)

13-13: V2RevertTransactionRequest docs correctly expose SchemaVersion

The SchemaVersion field is documented as an optional string used for validation with example v1.0.0, which lines up with the new query parameter on V2RevertTransactionRequest. Looks consistent with the rest of the schema-related API docs.

pkg/client/docs/models/components/v2log.md (1)

12-13: V2Log documentation now correctly surfaces SchemaVersion

Adding the SchemaVersion row as an optional string (“Schema version used for validation”, example v1.0.0) matches the V2Log struct and the semantics used elsewhere in the PR. No issues here.

test/e2e/api_schema_test.go (1)

37-44: Good end-to-end coverage of ledger + schema lifecycle; verify DB/ledger isolation

The combination of BeforeEach creating the "default" ledger and the inner BeforeEach inserting the three schema versions gives nice, repeatable setup for all specs under this When. This relies on the UseTemplatedDatabase/DeferTestServer harness providing effective isolation so repeated CreateLedger("default") and InsertSchema("v1.0.0".."v3.0.0") remain idempotent at the test level (no cross-spec leakage causing “already exists” failures). Please double‑check that the test DB is reset or uniquely namespaced per spec, as done in other e2e suites, so these expectations stay stable.

Also applies to: 54-81, 83-136

pkg/client/docs/models/components/v2bulkelementreverttransaction.md (1)

6-11: Bulk revert transaction docs correctly include SchemaVersion

The SchemaVersion field is added as an optional string with the expected “Schema version to use for validation” description and example. This matches the corresponding bulk component model and keeps bulk revert docs aligned with other bulk element types.

pkg/client/models/operations/v2reverttransaction.go (1)

21-25: SchemaVersion query param and accessor follow existing client patterns

The new SchemaVersion *string query parameter on V2RevertTransactionRequest and its GetSchemaVersion() accessor are consistent with the existing DryRun/Force/AtEffectiveDate fields and getters, and with the documented API surface. Assuming this file was regenerated via the usual Speakeasy flow, the change looks correct and future‑proof.

Also applies to: 73-78

pkg/client/models/components/v2bulkelementaddmetadata.go (1)

35-59: LGTM! Schema version field addition is consistent.

The generated code properly adds the optional SchemaVersion field with appropriate JSON tags and a nil-safe accessor that follows the existing pattern in this file.

internal/api/common/errors.go (2)

26-27: LGTM! Error constants follow established naming conventions.

The new schema-related error constants are consistent with existing error constant patterns in this file.


50-58: LGTM! Schema error handling is properly integrated.

The new error mappings correctly handle schema-related errors:

  • Schema validation errors map to BadRequest with ErrValidation code (grouped with idempotency validation)
  • Missing schema specification maps to BadRequest with ErrSchemaNotSpecified code
  • Schema not found maps to NotFound

The distinction between ErrValidation (line 52) for validation failures and ErrSchemaNotSpecified (line 56) for missing parameters provides clear error categorization for API clients.

pkg/client/models/components/v2bulkelementdeletemetadata.go (1)

35-59: LGTM! Consistent with other bulk element types.

The generated SchemaVersion field and accessor follow the same pattern as other V2 bulk element structures in this PR.

pkg/client/models/operations/v2addmetadatatoaccount.go (1)

22-61: LGTM! Query parameter configuration is appropriate.

The generated SchemaVersion query parameter with form style and explode=true follows standard OpenAPI/REST conventions for optional string parameters.

pkg/client/models/components/v2bulkelementreverttransaction.go (1)

51-75: LGTM! Consistent schema version integration.

The SchemaVersion field and accessor are properly generated, following the same pattern as other V2 bulk element types.

pkg/client/models/components/v2bulkelementcreatetransaction.go (1)

8-32: LGTM! Schema version field properly integrated.

The generated code adds SchemaVersion support to transaction creation bulk elements, maintaining consistency with the broader schema versioning feature.

internal/api/v2/controllers_schema_insert_test.go (1)

17-28: LGTM! Well-structured test with proper isolation.

The test uses parallel execution and a clear test case structure with all necessary parameters for comprehensive validation of the schema insertion endpoint.

internal/api/bulking/bulker.go (1)

137-142: No issues found—SchemaVersion field is properly defined in BulkElement struct.

The BulkElement struct at internal/api/bulking/elements.go:26 includes the SchemaVersion field as a string, confirming the code correctly accesses this field across all six ledger controller operations in bulker.go.

internal/chart_test.go (2)

11-245: Chart validation test table looks solid

The validation table covers a good spread of success and failure modes, including JSON round‑trip and exact error messages. No issues from my side.


247-286: Shared test chart fixture is well-structured

testChart nicely mirrors realistic account hierarchies (root, bank, users) and is reused consistently across tests. Looks good.

internal/controller/ledger/log_process.go (2)

71-93: Confirm assumptions around NeedsSchema and FindSchemas query

The schema gating flow looks correct overall (explicit lookup by SchemaVersion and a guard that blocks schema‑needing payloads once any schema exists). Two implicit assumptions are worth checking:

  • payload.NeedsSchema() is called on a zero‑value OUTPUT. This is fine if NeedsSchema is a pure type-level flag (e.g. always true for NewTransaction payloads and never inspects instance fields), but will be wrong if any implementation uses instance data.
  • FindSchemas is called with InitialPaginatedQuery[any]{PageSize: 1} and all other fields left at zero values. Ensure the storage layer tolerates an empty Column/Order here and still behaves as intended.

If either assumption doesn’t hold, you may want to adjust the contract of NeedsSchema or the query construction.


102-108: Schema version stamping and validation on logs looks correct

Assigning log.SchemaVersion = parameters.SchemaVersion and validating via log.ValidateWithSchema(*schema) when a schema is present correctly ties each log to the version it was checked against and prevents inserting invalid payloads.

pkg/client/models/components/v2log.go (1)

15-41: UPDATED_SCHEMA enum and schemaVersion field are wired consistently

The new V2LogTypeUpdatedSchema constant, its JSON unmarshalling, and the optional SchemaVersion field/accessor on V2Log all follow the existing patterns for other log types and fields. Looks good.

Also applies to: 48-56, 103-109

internal/controller/ledger/controller_default_test.go (1)

138-141: Schema pagination expectations in revert/metadata tests match new behavior

The added FindSchemas expectations in TestRevertTransaction, TestSaveTransactionMetadata, and TestDeleteTransactionMetadata correctly assert that these operations consult schema presence (and tolerate an empty schema set) when no SchemaVersion is provided. This aligns the tests with the new schema‑aware log processing.

Also applies to: 198-201, 248-251

internal/controller/ledger/controller_default.go (3)

54-61: Schema log processor wiring matches existing patterns

updateSchemaLp is initialized and used consistently with the other logProcessor[...] fields, so UpdateSchema will be logged and processed through the same pipeline (idempotency, hashing, events) as transactions and metadata.

Also applies to: 163-170


63-82: UpdateSchema flow and duplicate handling look sound

The helper builds a Schema via ledger.NewSchema, inserts it, and maps postgres.ErrConstraintsFailed{} to newErrSchemaAlreadyExists, which is the expected behavior for versioned schemas. The constructor ensures basic validity and InsertSchema mutates the pointer to populate timestamps, so returning ledger.UpdatedSchema{Schema: schema} should expose a fully-populated schema.


84-89: Schema read/list methods are thin, appropriate pass‑throughs

GetSchema and ListSchemas simply delegate to the store, reusing the existing pagination abstractions, which keeps the controller surface minimal.

internal/log.go (3)

88-102: SchemaVersion field and hashing remain backward compatible

The new SchemaVersion field is included in the hash payload with json:"schemaVersion,omitempty", so pre‑existing logs (with empty schemaVersion) hash identically, while new logs signed with a non‑empty version will have the version bound into their hash. That meets the requirement of extending integrity guarantees without breaking historic chains.

Also applies to: 146-183


190-205: LogPayload schema hooks and CreatedTransaction validation integrate cleanly

Extending LogPayload with NeedsSchema() and ValidateWithSchema(schema Schema) and implementing CreatedTransaction.ValidateWithSchema by delegating to schema.Chart.ValidatePosting(posting) provides a clear, focused contract for schema‑aware validation of postings. The Log.ValidateWithSchema wrapper keeps the call site simple.

Also applies to: 213-237


418-434: UpdatedSchema payload and hydration are consistent with events and controller

The UpdatedSchema payload struct matches the events shape (single schema field), implements the LogPayload contract, and is integrated into HydrateLog so UpdatedSchemaLogType logs round‑trip correctly between storage and in‑memory representations.

Also applies to: 435-457

docs/api/README.md (1)

313-369: Schema API surface and schemaVersion propagation in docs look consistent

The new endpoints for inserting, reading, and listing schemas, the optional schemaVersion query parameter on bulk/transaction/metadata operations, and the schemaVersion field on V2Log/V2Bulk elements match the internal Schema/ChartOfAccounts model and the Log.SchemaVersion field. The chart examples (e.g., $userID with .pattern) align with ChartSegment/ChartVariableSegment semantics, and the added error codes (SCHEMA_ALREADY_EXISTS, SCHEMA_NOT_SPECIFIED, OUTDATED_SCHEMA) mirror the controller’s schema error types.

Also applies to: 375-503, 3338-3351, 3865-3887, 4364-4399, 5451-5695

internal/README.md (1)

19-69: Generated docs correctly expose new chart/schema and log APIs

The gomarkdoc output for ValidateSegment, chart types, schema types, UpdatedSchema, Log.SchemaVersion, and the new schema‑aware helpers reflects the current code structure and line locations, which is what we want from generated internal documentation.

Also applies to: 212-256, 318-323, 418-447, 579-621, 711-737, 798-824, 1007-1111, 1524-1555, 1787-1823

internal/chart.go (4)

10-42: Segment naming and root invariants are well‑defined

ChartSegmentRegexp and ValidateSegment enforce a tight segment charset, and ChartOfAccounts.UnmarshalJSON correctly forbids variable ($...) and property (.self, .metadata, etc.) keys at the root level, which keeps the chart structure predictable and avoids ambiguous top‑level accounts.


70-164: ChartSegment.UnmarshalJSON correctly models accounts, fixed/variable segments, and patterns

The parser:

  • Validates subsegment keys,
  • Extracts and validates .pattern as a regex,
  • Enforces that only $... segments may carry patterns and that variable segments must have one,
  • Prevents multiple variable segments at the same level,
  • Treats leaves (no subsegments) or segments with .self as accounts and attaches .metadata/.rules accordingly.

This yields a clean, unambiguous in‑memory model for both fixed and variable account paths.


178-222: Marshalling logic is symmetric with unmarshalling

marshalJsonObject, ChartSegment.MarshalJSON, and ChartVariableSegment.MarshalJSON rebuild the JSON structure with:

  • Fixed children and a single $label variable child,
  • Optional .metadata and .rules for accounts,
  • .self only when a segment is both an account and has children,
  • .pattern attached only to variable segments.

This matches the expectations enforced in UnmarshalJSON, so round‑tripping a chart preserves structure.


252-273: ValidatePosting uses the chart consistently for source and destination

ChartOfAccounts.ValidatePosting simply delegates to FindAccountSchema for both posting.Source and posting.Destination, letting the chart definition (plus the world special‑case) drive validity. That keeps schema enforcement centralized and makes it easy to evolve account rules later.


// stateless version (+1 regarding directory name, as migrations start from 1 in the lib)
const MinimalSchemaVersion = 26
const MinimalSchemaVersion = 42
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify migration files exist and document this breaking change.

The minimal schema version jumped from 26 to 42, requiring 16 new migrations. This is a breaking change—any database below version 42 will fail the HasMinimalVersion check. Ensure all migration files 27-42 exist, are properly tested, and that upgrade documentation is provided for existing deployments.

Run the following script to verify the migration files and documentation:


🏁 Script executed:

#!/bin/bash
# Description: Verify all migration files 27-42 exist and check for upgrade documentation

echo "=== Checking for migration files 27-42 ==="
# Based on the comment, migrations are in directories. Find the migrations directory.
MIGRATIONS_DIR=$(fd -t d -d 3 'migrations' | head -1)

if [ -z "$MIGRATIONS_DIR" ]; then
  echo "ERROR: Could not find migrations directory"
  exit 1
fi

echo "Found migrations directory: $MIGRATIONS_DIR"
echo ""

# Check for migration directories/files from 27 to 42
MISSING_MIGRATIONS=""
for i in {27..42}; do
  # Try to find directory or file matching the version
  if ! fd -d 1 "^${i}$" "$MIGRATIONS_DIR" | grep -q .; then
    MISSING_MIGRATIONS="$MISSING_MIGRATIONS $i"
  fi
done

if [ -n "$MISSING_MIGRATIONS" ]; then
  echo "ERROR: Missing migration versions:$MISSING_MIGRATIONS"
else
  echo "SUCCESS: All migration versions 27-42 found"
fi

echo ""
echo "=== Migration files structure ==="
fd -d 1 . "$MIGRATIONS_DIR" | sort -V | tail -20

echo ""
echo "=== Checking for upgrade/migration documentation ==="
# Look for documentation mentioning version 42 or migration guides
rg -l "version.*42|schema.*version|migration.*guide|upgrade.*guide" --type=md -g '!**/node_modules/**' | head -10

echo ""
echo "=== Checking for references to old MinimalSchemaVersion ==="
# Check if there are any lingering references to version 26
rg -n "MinimalSchemaVersion.*26" --type=go

Length of output: 529


I need to find the actual production migrations directory and verify the migration structure. The test directory doesn't contain the production migration files.


---

🏁 Script executed:

```shell
#!/bin/bash
# Find all directories and files related to migrations in the codebase

echo "=== Finding all migration-related directories ==="
fd -t d 'migrations' -H

echo ""
echo "=== Finding all migration-related files ==="
fd -t f 'migration' -H

echo ""
echo "=== Checking internal/storage directory structure ==="
fd -t d . internal/storage -d 3

echo ""
echo "=== Looking for SQL migration files or version-numbered files ==="
fd -e 'sql|go' 'internal/storage' | head -20

echo ""
echo "=== Checking for any files/directories with version numbers (like 001, 026, etc) ==="
fd -P '^\d+' . internal/storage

echo ""
echo "=== Searching for migration or schema references ==="
rg -n "migrate|schema" internal/storage/bucket/default_bucket.go -C 3

Length of output: 4270


Good! I found the production migrations directory. Migrations 27-41 all exist, but migration 42 is missing. Let me verify the migration loading logic and understand the versioning scheme:


---

🏁 Script executed:

```shell
#!/bin/bash

echo "=== Listing all migration directories in detail ==="
ls -1 internal/storage/bucket/migrations/ | grep -E '^\d+' | sort -V

echo ""
echo "=== Checking migrations.go to understand version loading ==="
cat internal/storage/bucket/migrations.go | head -100

echo ""
echo "=== Checking if migration 42 exists with different naming pattern ==="
find internal/storage/bucket/migrations -name "*42*" -o -name "*schema*" | sort

echo ""
echo "=== Checking the highest numbered migration ==="
ls -1d internal/storage/bucket/migrations/*/ | tail -5

echo ""
echo "=== Looking at default_bucket.go around MinimalSchemaVersion ==="
rg -A 5 -B 5 "MinimalSchemaVersion" internal/storage/bucket/default_bucket.go

Length of output: 3613


Critical issue identified: MinimalSchemaVersion is set to 42, but migration 42 doesn't exist. The highest migration is 41-add-schema. Let me verify if migration 42 is uncommitted or if this is genuinely missing:


---

🏁 Script executed:

```shell
#!/bin/bash

echo "=== Git status for migrations directory ==="
git status internal/storage/bucket/migrations/

echo ""
echo "=== Check for any migration 42 files ==="
find internal/storage/bucket/migrations -type f -o -type d | grep 42

echo ""
echo "=== List all migration directories (complete listing) ==="
ls -1d internal/storage/bucket/migrations/*/ 2>/dev/null | wc -l
ls -1d internal/storage/bucket/migrations/*/ 2>/dev/null | tail -10

echo ""
echo "=== Verify PR changes affecting migrations ==="
git diff --name-only | grep -i migration

echo ""
echo "=== Check if migration 42 is in any branch/stash ==="
git log --all --oneline -- '*migrations/42*' 2>/dev/null | head -5

Length of output: 997


Migration 42 does not exist—this is a critical bug that will break all deployments.

The MinimalSchemaVersion was changed to 42, but only migrations 0-41 exist (42 total directories). No database can reach version 42 if the migration doesn't exist, causing HasMinimalVersion to always return false. The PR makes no changes to migration files.

You must either:

  1. Create and add migration 42 to internal/storage/bucket/migrations/, OR
  2. Revert MinimalSchemaVersion from 42 to 41
🤖 Prompt for AI Agents
internal/storage/bucket/default_bucket.go around line 18: MinimalSchemaVersion
was changed to 42 but no migration 42 exists, causing HasMinimalVersion to
always fail; either revert this constant to 41 or add a proper migration #42 in
internal/storage/bucket/migrations/. To fix, choose one: (A) revert the constant
back to 41 so the code matches existing migrations (quick fix), or (B) add a new
migration directory named 42 with the required up/down SQL/Go migration files
following the project's migration naming/format conventions and update any
migration registry so the migration runner recognizes version 42. Ensure tests
and migration runner validate the new version before merging.

@Azorlogh Azorlogh force-pushed the feat/schema branch 4 times, most recently from db8de5e to 3476baf Compare November 18, 2025 14:46
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/api/common/errors.go (1)

17-36: Add HTTP mapping for ErrSchemaAlreadyExists to avoid 500s on conflict

You define ErrSchemaAlreadyExists = "SCHEMA_ALREADY_EXISTS" but HandleCommonWriteErrors never maps the corresponding controller error (e.g. ledgercontroller.ErrSchemaAlreadyExists{}), so a schema-insert conflict will currently fall through to HandleCommonErrors and return a 500 instead of a client error.

Consider adding an explicit case, analogous to other “already exists” errors:

 func HandleCommonWriteErrors(w http.ResponseWriter, r *http.Request, err error) {
 	switch {
 	case errors.Is(err, ledgercontroller.ErrIdempotencyKeyConflict{}):
 		api.WriteErrorResponse(w, http.StatusConflict, ErrConflict, err)
 	case errors.Is(err, ledgercontroller.ErrInvalidIdempotencyInput{}) ||
 		errors.Is(err, ledgercontroller.ErrSchemaValidationError{}):
 		api.BadRequest(w, ErrValidation, err)
+	case errors.Is(err, ledgercontroller.ErrSchemaAlreadyExists{}):
+		api.WriteErrorResponse(w, http.StatusConflict, ErrSchemaAlreadyExists, err)
 	case errors.Is(err, ledgercontroller.ErrNotFound):
 		api.NotFound(w, err)
 	case errors.Is(err, ledgercontroller.ErrSchemaNotSpecified{}):
 		api.BadRequest(w, ErrSchemaNotSpecified, err)
 	case errors.Is(err, ledgercontroller.ErrSchemaNotFound{}):
 		api.NotFound(w, err)
 	default:
 		HandleCommonErrors(w, r, err)
 	}
 }

Adjust the status code if you prefer 400 over 409, but mapping this error explicitly will keep the API behavior consistent with other “already exists” scenarios.

Also applies to: 47-63

♻️ Duplicate comments (1)
internal/controller/ledger/controller_with_traces.go (1)

474-495: Fix span name to match method name.

The span name at line 482 is "UpdatedSchema" but should be "UpdateSchema" to match the method name. This inconsistency affects trace readability and filtering.

Apply this diff:

 	_, err = tracing.TraceWithMetric(
 		ctx,
-		"UpdatedSchema",
+		"UpdateSchema",
 		c.tracer,
 		c.updateSchemaHistogram,
 		func(ctx context.Context) (any, error) {

Note: This issue was previously flagged but appears to still be present in the current code.

🧹 Nitpick comments (6)
docs/api/README.md (1)

4553-4554: New error codes properly documented. SCHEMA_ALREADY_EXISTS and SCHEMA_NOT_SPECIFIED are added to V2ErrorsEnum with appropriate placement. Descriptions would improve usability—consider adding brief notes on when each error occurs (e.g., "SCHEMA_ALREADY_EXISTS: returned when attempting to insert a schema with an existing version").

internal/chart.go (1)

38-42: Chart parsing and account resolution look solid; only minor optimizations possible

The JSON (un)marshalling logic for ChartOfAccounts, ChartSegment, and ChartVariableSegment enforces the documented invariants (no variable root, single variable segment per prefix, .self shape, pattern validation) and the resolution path in findAccountSchema/FindAccountSchema behaves as expected, including the special handling of "world". Error propagation via ErrInvalidAccount is clear and preserves context.

If you ever profile this, you might consider (optional) improvements:

  • Cache a compiled *regexp.Regexp on ChartVariableSegment instead of calling regexp.Match on the pattern string for every lookup.
  • Rename the inner segment maps in UnmarshalJSON to avoid shadowing the outer segment variable and slightly improve readability.

These are purely optional; the current implementation is correct and maintainable.

Also applies to: 70-164, 178-205, 224-273

internal/controller/ledger/log_process.go (1)

87-95: Use of FindSchemas with InitialPaginatedQuery is reasonable but implicit

The existence check:

schemas, err := store.FindSchemas(ctx, storagecommon.InitialPaginatedQuery[any]{PageSize: 1})
if err != nil {
    return nil, nil, err
}
if len(schemas.Data) > 0 {
    return nil, nil, newErrSchemaNotSpecified()
}

is a neat way to cheaply test “does this ledger have any schemas?”. It does, however, rely on FindSchemas treating an empty Column/Order in InitialPaginatedQuery as “use defaults” rather than returning a pagination error.

Consider either:

  • Explicitly setting a default sort column here (e.g., "created_at"), or
  • Adding a brief comment where FindSchemas is implemented to guarantee that empty Column is supported.

This makes the dependency on store behavior clearer.

internal/api/v2/controllers_schema_list.go (1)

17-33: Consider constraining sort to allowed values at the API level

Your concern is valid. Currently, any sort value is accepted at the API level and only validated downstream. While the store-level validation via ErrNotPaginatedField ensures safety, early validation at the API level would improve clarity and fail-fast semantics.

The schema resource defines only two paginated fields: "version" and "created_at" (internal/storage/ledger/resource_schemas.go:19-20). Mapping the sort query parameter to this explicit set before calling getPaginatedQuery would make the contract self-documenting and prevent unexpected values from reaching the database layer.

internal/log.go (2)

20-26: UpdatedSchema log type wiring looks consistent; align the comment string

The new UpdatedSchemaLogType is correctly mapped to "UPDATED_SCHEMA" in String() and LogTypeFromString(), so behavior is coherent. Minor nit: the comment on Line 25 still says "UPDATE_CHART_OF_ACCOUNT", which no longer matches the actual string and could confuse readers or tooling that rely on these comments for documentation.

Consider updating the comment to "UPDATED_SCHEMA" (or dropping it) for consistency.

Also applies to: 54-69, 71-85


273-279: Clarify intentions for no-op schema validations on metadata/revert log types

For SavedMetadata, DeletedMetadata, and RevertedTransaction, NeedsSchema() returns true but ValidateWithSchema is a no‑op returning nil. This is valid, but slightly surprising:

  • If the intent is only to require the presence of a schema (e.g., to attach a SchemaVersion to these logs) without enforcing any specific rules yet, a short comment on each method would make this explicit.
  • If these log types should not depend on a schema at all, consider flipping NeedsSchema() to false so log processing doesn’t unnecessarily fetch/expect a schema.

Either approach is fine; documenting the current behavior will prevent confusion for future maintainers.

Also applies to: 325-331, 376-383

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 48305f2 and 3476baf.

⛔ Files ignored due to path filters (1)
  • docs/events/UpdatedSchema.json is excluded by !**/*.json
📒 Files selected for processing (39)
  • cmd/docs_events.go (1 hunks)
  • docs/api/README.md (22 hunks)
  • internal/README.md (37 hunks)
  • internal/api/bulking/bulker.go (6 hunks)
  • internal/api/bulking/elements.go (1 hunks)
  • internal/api/bulking/mocks_ledger_controller_test.go (3 hunks)
  • internal/api/common/errors.go (2 hunks)
  • internal/api/common/mocks_ledger_controller_test.go (3 hunks)
  • internal/api/v1/mocks_ledger_controller_test.go (3 hunks)
  • internal/api/v2/controllers_schema_insert.go (1 hunks)
  • internal/api/v2/controllers_schema_insert_test.go (1 hunks)
  • internal/api/v2/controllers_schema_list.go (1 hunks)
  • internal/api/v2/controllers_schema_list_test.go (1 hunks)
  • internal/api/v2/controllers_schema_read.go (1 hunks)
  • internal/api/v2/controllers_schema_read_test.go (1 hunks)
  • internal/api/v2/mocks_ledger_controller_test.go (3 hunks)
  • internal/api/v2/query.go (1 hunks)
  • internal/api/v2/routes.go (1 hunks)
  • internal/api/v2/views.go (1 hunks)
  • internal/bus/listener.go (1 hunks)
  • internal/chart.go (1 hunks)
  • internal/chart_test.go (1 hunks)
  • internal/controller/ledger/controller.go (2 hunks)
  • internal/controller/ledger/controller_default.go (3 hunks)
  • internal/controller/ledger/controller_default_test.go (7 hunks)
  • internal/controller/ledger/controller_generated_test.go (3 hunks)
  • internal/controller/ledger/controller_with_events.go (1 hunks)
  • internal/controller/ledger/controller_with_too_many_client_handling.go (2 hunks)
  • internal/controller/ledger/controller_with_traces.go (3 hunks)
  • internal/controller/ledger/errors.go (1 hunks)
  • internal/controller/ledger/listener.go (1 hunks)
  • internal/controller/ledger/listener_generated_test.go (1 hunks)
  • internal/controller/ledger/log_process.go (2 hunks)
  • internal/controller/ledger/log_process_test.go (4 hunks)
  • internal/controller/ledger/parameters.go (1 hunks)
  • internal/controller/ledger/store.go (2 hunks)
  • internal/controller/ledger/store_generated_test.go (3 hunks)
  • internal/errors.go (1 hunks)
  • internal/log.go (13 hunks)
🚧 Files skipped from review as they are similar to previous changes (14)
  • internal/bus/listener.go
  • internal/controller/ledger/parameters.go
  • internal/chart_test.go
  • internal/api/bulking/bulker.go
  • internal/controller/ledger/controller_with_too_many_client_handling.go
  • internal/api/bulking/mocks_ledger_controller_test.go
  • internal/errors.go
  • internal/api/v2/controllers_schema_insert.go
  • internal/controller/ledger/store.go
  • internal/api/v2/controllers_schema_insert_test.go
  • internal/api/v2/controllers_schema_list_test.go
  • internal/api/bulking/elements.go
  • internal/controller/ledger/listener_generated_test.go
  • internal/api/v2/controllers_schema_read_test.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-04-29T11:24:28.923Z
Learnt from: gfyrag
Repo: formancehq/ledger PR: 892
File: internal/controller/ledger/controller_default.go:196-196
Timestamp: 2025-04-29T11:24:28.923Z
Learning: In the ledger Import function, it's critical to maintain proper log ID tracking by updating lastLogID with the current log.ID after each processed log, rather than setting it to nil. This ensures the system can properly validate the ordering of logs and prevent duplicate or out-of-order processing, which is essential for maintaining data integrity in the ledger.

Applied to files:

  • internal/controller/ledger/controller_default.go
  • internal/README.md
🧬 Code graph analysis (21)
internal/controller/ledger/log_process_test.go (1)
internal/schema.go (1)
  • Schema (15-21)
internal/api/common/errors.go (1)
internal/controller/ledger/errors.go (5)
  • ErrInvalidIdempotencyInput (221-225)
  • ErrSchemaValidationError (268-271)
  • ErrNotFound (14-14)
  • ErrSchemaNotSpecified (289-289)
  • ErrSchemaNotFound (249-251)
internal/controller/ledger/errors.go (1)
internal/api/common/errors.go (2)
  • ErrSchemaNotSpecified (28-28)
  • ErrSchemaAlreadyExists (27-27)
internal/controller/ledger/controller_generated_test.go (5)
internal/schema.go (1)
  • Schema (15-21)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (26-29)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (135-138)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/log.go (1)
  • UpdatedSchema (418-420)
internal/api/v2/views.go (1)
internal/schema.go (1)
  • Schema (15-21)
internal/controller/ledger/log_process.go (4)
internal/schema.go (1)
  • Schema (15-21)
internal/controller/ledger/errors.go (1)
  • ErrNotFound (14-14)
internal/storage/common/pagination.go (1)
  • InitialPaginatedQuery (10-15)
internal/log.go (2)
  • NewLog (194-199)
  • ComputeIdempotencyHash (459-468)
internal/controller/ledger/store_generated_test.go (2)
internal/schema.go (1)
  • Schema (15-21)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (26-29)
internal/api/v2/mocks_ledger_controller_test.go (5)
internal/api/bulking/mocks_ledger_controller_test.go (2)
  • LedgerController (26-30)
  • LedgerControllerMockRecorder (33-35)
internal/schema.go (1)
  • Schema (15-21)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (26-29)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/log.go (2)
  • Log (89-102)
  • UpdatedSchema (418-420)
internal/controller/ledger/controller.go (5)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/log.go (2)
  • Log (89-102)
  • UpdatedSchema (418-420)
pkg/events/message.go (1)
  • UpdatedSchema (77-80)
internal/schema.go (2)
  • Schema (15-21)
  • SchemaData (11-13)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (26-29)
internal/api/v1/mocks_ledger_controller_test.go (9)
internal/api/bulking/mocks_ledger_controller_test.go (2)
  • LedgerController (26-30)
  • LedgerControllerMockRecorder (33-35)
internal/api/common/mocks_ledger_controller_test.go (2)
  • LedgerController (26-30)
  • LedgerControllerMockRecorder (33-35)
internal/api/v2/mocks_ledger_controller_test.go (2)
  • LedgerController (26-30)
  • LedgerControllerMockRecorder (33-35)
internal/schema.go (1)
  • Schema (15-21)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (26-29)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (135-138)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/log.go (2)
  • Log (89-102)
  • UpdatedSchema (418-420)
pkg/events/message.go (1)
  • UpdatedSchema (77-80)
internal/log.go (3)
internal/storage/ledger/logs.go (1)
  • Log (20-26)
internal/schema.go (1)
  • Schema (15-21)
pkg/events/message.go (4)
  • SavedMetadata (27-32)
  • DeletedMetadata (60-65)
  • RevertedTransaction (44-48)
  • UpdatedSchema (77-80)
internal/api/common/mocks_ledger_controller_test.go (5)
internal/schema.go (1)
  • Schema (15-21)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (26-29)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (135-138)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/log.go (1)
  • UpdatedSchema (418-420)
internal/controller/ledger/controller_default.go (5)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (135-138)
internal/log.go (2)
  • UpdatedSchema (418-420)
  • Log (89-102)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/controller/ledger/store.go (1)
  • Store (29-66)
internal/schema.go (2)
  • NewSchema (23-33)
  • Schema (15-21)
internal/chart.go (1)
internal/errors.go (1)
  • ErrInvalidAccount (102-105)
internal/controller/ledger/listener.go (3)
internal/log.go (1)
  • UpdatedSchema (418-420)
pkg/events/message.go (1)
  • UpdatedSchema (77-80)
internal/schema.go (1)
  • Schema (15-21)
cmd/docs_events.go (1)
internal/log.go (1)
  • UpdatedSchema (418-420)
internal/api/v2/controllers_schema_read.go (1)
internal/api/common/errors.go (1)
  • HandleCommonErrors (38-45)
internal/controller/ledger/controller_with_traces.go (6)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (135-138)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/log.go (1)
  • UpdatedSchema (418-420)
internal/tracing/tracing.go (1)
  • TraceWithMetric (101-129)
internal/schema.go (1)
  • Schema (15-21)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (26-29)
internal/controller/ledger/controller_default_test.go (4)
internal/schema.go (2)
  • Schema (15-21)
  • SchemaData (11-13)
internal/chart.go (2)
  • ChartOfAccounts (36-36)
  • ChartAccount (12-15)
internal/controller/ledger/controller.go (2)
  • CreateTransaction (102-106)
  • RunScript (91-91)
internal/machine/vm/run.go (1)
  • RunScript (14-19)
internal/controller/ledger/controller_with_events.go (5)
internal/controller/ledger/controller.go (2)
  • UpdateSchema (135-138)
  • Controller (21-89)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/log.go (2)
  • Log (89-102)
  • UpdatedSchema (418-420)
pkg/events/message.go (1)
  • UpdatedSchema (77-80)
internal/schema.go (1)
  • Schema (15-21)
internal/api/v2/controllers_schema_list.go (4)
internal/api/common/pagination.go (1)
  • PaginationConfig (3-6)
pkg/client/models/operations/v2listschemas.go (3)
  • Order (37-37)
  • OrderDesc (41-41)
  • OrderAsc (40-40)
internal/api/common/errors.go (2)
  • ErrValidation (20-20)
  • HandleCommonPaginationErrors (65-74)
internal/schema.go (1)
  • Schema (15-21)
🪛 markdownlint-cli2 (0.18.1)
docs/api/README.md

357-357: Blank line inside blockquote

(MD028, no-blanks-blockquote)


403-403: Blank line inside blockquote

(MD028, no-blanks-blockquote)


461-461: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4

(MD001, heading-increment)


470-470: Blank line inside blockquote

(MD028, no-blanks-blockquote)


5624-5624: Multiple headings with the same content

(MD024, no-duplicate-heading)


5626-5626: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


5648-5648: Multiple headings with the same content

(MD024, no-duplicate-heading)


5679-5679: Multiple headings with the same content

(MD024, no-duplicate-heading)


5707-5707: Multiple headings with the same content

(MD024, no-duplicate-heading)


5739-5739: Multiple headings with the same content

(MD024, no-duplicate-heading)


5773-5773: Multiple headings with the same content

(MD024, no-duplicate-heading)


5812-5812: Multiple headings with the same content

(MD024, no-duplicate-heading)


5849-5849: Multiple headings with the same content

(MD024, no-duplicate-heading)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Tests
🔇 Additional comments (29)
cmd/docs_events.go (1)

39-39: LGTM! UpdatedSchema event type added to documentation generation.

The addition follows the established pattern and correctly integrates the new UpdatedSchema event type into the JSON schema documentation workflow.

docs/api/README.md (6)

318-509: New schema management endpoints are well-documented. The three endpoints (Insert, Get, List) include proper parameter descriptions, example requests/responses, and authentication scopes. Documentation structure is consistent with existing endpoints.


690-690: schemaVersion parameter consistently documented across endpoints. The parameter is added to bulk operations, transaction creation, metadata operations, and revert operations with clear descriptions indicating it's optional for validation. Good consistency in parameter placement and documentation.

Also applies to: 734-734, 1139-1139, 1566-1566, 1842-1842, 1907-1907


3506-3507: schemaVersion field properly integrated into V2Log schema. The field is marked as optional (false in Required column at line 4043) with clear description "Schema version used for validation". New enum value UPDATED_SCHEMA at line 4053 is properly documented. Documentation is consistent with parameter additions elsewhere.

Also applies to: 4028-4029, 4043-4043, 4053-4053


4667-4667: schemaVersion added consistently across all bulk operation element types. Present in V2BaseBulkElement (base structure) and propagated to all derived types (CreateTransaction, AddMetadata, RevertTransaction, DeleteMetadata). Marked as optional with description "Schema version to use for validation". Bulk request endpoint parameter documentation at line 734 explains the interaction between endpoint-level and element-level schemaVersion.

Also applies to: 4720-4732, 4745-4745, 4818-4818, 4931-4931, 4974-4974, 5017-5017


461-461: The review comment is incorrect. No markdown linting violation exists.

The heading structure shows a proper hierarchy: line 451 contains an h3 heading ("Parameters"), and line 461 contains an h4 heading ("Enumerated Values"). This represents a valid increment of one level (h3 → h4), which is compliant with the MD001 rule. The h4 serves as a logical subsection under the h3 "Parameters" section, making the structure both syntactically correct and semantically appropriate. No changes are needed.

Likely an incorrect or invalid review comment.


5624-5626: Rewritten Review Comment — Review Claim Is Incorrect

The cited lines (5648, 5679, 5707, 5739, 5773, 5812, 5849) do not exhibit the issue described. These lines correctly show "### Properties" headings followed by proper documentation tables with fields like Name, Type, Required, Restrictions, and Description.

Only line 5624-5626 matches the pattern flagged—it contains "### Properties" followed by "None" without a properties table. If this single instance requires attention, address it at that location only; the other cited lines have appropriate structure and do not need changes.

Likely an incorrect or invalid review comment.

internal/controller/ledger/store_generated_test.go (1)

15-24: Schema-related mocks are consistent with the Store interface

The added FindSchema, FindSchemas, and InsertSchema mocks (and recorders) follow the existing gomock patterns and match the expected signatures for schema persistence and querying. No issues from a correctness or testing perspective.

Also applies to: 152-174, 226-238

internal/README.md (1)

19-40: Generated docs accurately reflect the expanded chart/schema/log surface

The gomarkdoc output for the new chart types, schema types, log SchemaVersion field, and related methods (e.g., ValidateSegment, FindAccountSchema, UpdatedSchema) is coherent and aligns with the corresponding code snippets in internal/chart.go, internal/schema.go, and internal/log.go. No actionable issues here.

Also applies to: 418-527, 1008-1159, 1524-1557, 1788-1821

internal/api/v2/query.go (1)

11-17: SchemaVersion wiring into command parameters is straightforward

Threading schemaVersion from the query string into ledger.Parameters[INPUT].SchemaVersion is simple and side-effect free; downstream logic can decide how to treat the empty-string case. No issues here.

internal/api/v2/views.go (1)

146-148: renderSchema helper is consistent with existing view helpers

Returning the ledger.Schema as-is (ignoring HeaderBigInt-As-String, which is irrelevant for this type) matches the pattern of other render helpers and keeps the API surface uniform. Looks good.

internal/api/v2/routes.go (1)

62-88: Schema endpoints are wired correctly under the ledger scope

The new /schema routes (POST/GET by {version} and paginated GET) are mounted inside the ledger-scoped group with LedgerMiddleware and reuse the existing pagination config. This matches the patterns of other v2 resources (accounts, transactions, logs) and looks correct.

internal/controller/ledger/log_process_test.go (1)

12-19: Tests correctly stub the new FindSchemas call in log processing

The added bunpaginate import and FindSchemas expectations in both TestForgeLogWithIKConflict and TestForgeLogWithDeadlock align with the updated Store interface and ensure forgeLog’s new schema lookup path is satisfied without affecting the tests’ primary focus (idempotency conflict and deadlock retry). Looks good.

Also applies to: 36-39, 71-74, 84-87

internal/api/v2/controllers_schema_read.go (1)

14-29: readSchema handler looks consistent and correct

  • Correctly pulls ledger from context, reads version from path, and delegates to GetSchema.
  • 404 mapping via postgres.IsNotFoundError and fallback to HandleCommonErrors align with existing patterns.
internal/controller/ledger/controller_with_events.go (1)

156-168: UpdateSchema event wrapper matches existing patterns

The wrapper cleanly delegates to Controller.UpdateSchema, and, on success, emits UpdatedSchema events only when DryRun is false, mirroring the transaction/metadata handlers’ behavior.

internal/controller/ledger/listener.go (1)

12-18: Listener interface extension is coherent

Adding UpdatedSchema(ctx, ledger string, data ledger.Schema) is consistent with the other methods (ledger-first, payload second) and matches how ControllerWithEvents.UpdateSchema uses the listener.

internal/controller/ledger/controller.go (2)

83-88: Schema controller API surface is well-shaped

UpdateSchema, GetSchema, and ListSchemas follow existing controller conventions (generic Parameters, paginated queries, bunpaginate cursors) and provide a clear schema management API.


135-138: UpdateSchema input struct matches internal schema model

Using Version string plus Data ledger.SchemaData mirrors the ledger.Schema model and keeps the API payload tight around what’s actually needed for an update.

internal/api/v2/controllers_schema_list.go (1)

39-47: ListSchemas retrieval and rendering path looks good

Using ListSchemas with the constructed paginated query and piping through bunpaginate.MapCursor + renderSchema matches existing list endpoints and keeps the response shape consistent.

internal/controller/ledger/log_process.go (2)

101-110: Schema version tagging and validation in logs is correctly sequenced

  • log.SchemaVersion is set from parameters.SchemaVersion, preserving which schema governed the operation.
  • ValidateWithSchema is run before InsertLog, and any schema validation error is wrapped as newErrSchemaValidationError, so invalid payloads never get persisted.

This ordering keeps logs consistent with their governing schema.


73-95: Assumption is correct; current implementation is valid but document the contract

The review concern is valid from a maintainability perspective. The script output confirms all 5 NeedsSchema() implementations across CreatedTransaction, SavedMetadata, DeletedMetadata, RevertedTransaction, and UpdatedSchema return hardcoded boolean values with no field inspection—making them purely type-driven as assumed.

Calling NeedsSchema() on a zero-valued OUTPUT is therefore safe and correct. However, to prevent future breakage, document this contract: either add a comment to the LogPayload interface explaining that NeedsSchema() is type-driven and must not inspect fields, or add a comment in log_process.go explaining why the zero-value call is valid.

The suggested refactoring (calling on the real payload after fn()) is unnecessary—the type-only check is the cleaner design.

internal/controller/ledger/controller_default_test.go (1)

25-119: LGTM! Well-structured parameterized test.

The refactoring to parameterize testCreateTransaction with withSchema provides good coverage for both schema-present and schema-absent scenarios. The conditional store expectations and schema setup are correctly implemented.

internal/controller/ledger/controller_default.go (2)

57-84: LGTM! Schema operations properly implemented.

The new schema operations follow the established patterns:

  • UpdateSchema uses the forgeLog pattern consistently with other operations
  • Error handling properly detects duplicate schemas via constraint violations
  • GetSchema and ListSchemas appropriately delegate to the store layer
  • The updateSchema helper correctly handles NewSchema's error return

277-282: LGTM! Import handling for schemas added correctly.

The UpdatedSchema case properly inserts the schema during log import, consistent with how other log payloads are handled.

internal/controller/ledger/errors.go (1)

249-320: LGTM! Well-implemented error types.

The four new schema-related error types follow the established patterns in this file:

  • Clear, informative error messages
  • Type-safe Is() methods for error checking
  • Consistent naming and structure
  • Appropriate field capturing for context
internal/log.go (4)

92-102: SchemaVersion field and hashing: semantics look good, but depend on call order

Adding SchemaVersion to Log and including it in ComputeHash is a solid way to bind idempotency to the schema version. Using json:"schemaVersion,omitempty" in both the struct and the hash struct maintains backward compatibility: logs without a schema version will hash as before, while logs with a version will produce distinct hashes.

One thing to double‑check: SchemaVersion must be set on the Log before ComputeHash (e.g., before ChainLog) is called; otherwise, changing SchemaVersion later will not affect the hash and could undermine idempotency guarantees when re‑processing under a different schema.

Also applies to: 161-170, 171-178


190-192: Log.ValidateWithSchema delegation and LogPayload interface extension look coherent

Extending LogPayload with NeedsSchema and ValidateWithSchema and delegating via Log.ValidateWithSchema is a clean design; it keeps schema‑specific logic in the payload types while giving callers a uniform entry point.

As long as all concrete payload types in the ledger package implement the new methods (which this file appears to cover), this change is safe and improves flexibility.

Also applies to: 201-205


218-230: CreatedTransaction schema validation is straightforward and correct

CreatedTransaction.ValidateWithSchema iterates postings and delegates to schema.Chart.ValidatePosting, returning on the first error. Combined with NeedsSchema() == true, this is a sensible way to enforce chart‑of‑accounts rules on new transactions.

No issues from a correctness standpoint.


418-434: UpdatedSchema payload wiring matches the schema model and events

UpdatedSchema embedding a Schema under json:"schema" aligns with the event payload shape in pkg/events/message.go and the Schema definition in internal/schema.go. Having NeedsSchema() == false and a no‑op ValidateWithSchema is appropriate, since this log type defines the schema rather than relying on an existing one.

The LogPayload assertion ensures compile‑time safety. No changes needed.

internal/log.go Outdated
Comment on lines 446 to 445
case UpdatedSchemaLogType:
payload = &UpdatedSchema{}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Fix HydrateLog unmarshalling: avoid pointer-to-interface (currently unsafe)

HydrateLog currently does:

var payload any
// payload = &CreatedTransaction{} / &SavedMetadata{} / ...
err := json.Unmarshal(data, &payload)
return reflect.ValueOf(payload).Elem().Interface().(LogPayload), nil

Passing &payload (a *interface{}) to json.Unmarshal causes it to treat the destination as an interface sink and replace the value with generic types (e.g., map[string]any), not populate the concrete struct pointer you set. This makes the subsequent .Elem() unsafe and will typically either panic or yield the wrong type.

The standard pattern is to store a pointer to the concrete type in the interface and pass that directly to json.Unmarshal. Here, that means:

-    err := json.Unmarshal(data, &payload)
+    err := json.Unmarshal(data, payload)

This allows encoding/json to unmarshal into the concrete *CreatedTransaction, *SavedMetadata, etc., and keeps the final reflect.ValueOf(payload).Elem().Interface().(LogPayload) working as intended.

Also applies to: 451-456



---

🏁 Script executed:

```shell
#!/bin/bash
# Examine HydrateLog function in internal/log.go around lines 446-456
fd -e go internal/log.go --exec cat {} | head -500 | tail -100

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Search for HydrateLog function definition with context
rg -nP -C15 'func.*HydrateLog' internal/log.go

Length of output: 849


🏁 Script executed:

#!/bin/bash
# Look for json.Unmarshal usage in the HydrateLog context
rg -nP -C10 'json\.Unmarshal.*payload' internal/log.go

Length of output: 620


🏁 Script executed:

#!/bin/bash
# Check the payload assignment pattern around lines 446-456
sed -n '430,470p' internal/log.go

Length of output: 921



---

🌐 Web query:

```web
Go json.Unmarshal behavior pointer to interface vs interface type

💡 Result:

Short answer

  • json.Unmarshal expects its second argument v to be a pointer to something it can set. If you pass *interface{} (a pointer to an interface value), Unmarshal will set that interface to a newly-chosen concrete Go value (map[string]interface{}, []interface{}, float64, string, bool, or nil for JSON null). [1]
  • If you pass an interface{} that already contains a non-nil concrete pointer (for example var i interface{} = &MyStruct{} and you call json.Unmarshal(data, i)), the json package will unmarshal into the concrete value pointed-to by that pointer (it can reuse the pointer inside the interface). If the interface holds nil (no concrete value) Unmarshal will store a new concrete value (map/slice/number/etc) into the interface. [1][2]

Examples

  • var x interface{}; json.Unmarshal(data, &x)
    -> x becomes map[string]interface{} / []interface{} / float64 / string / bool / nil depending on JSON. [1]

  • var p *interface{}; json.Unmarshal(data, p)
    -> only valid if p points to an interface value; Unmarshal sets the interface value (same rules as above). Passing a nil *interface{} (uninitialized pointer) is an error/invalid. [1][5]

  • var s MyStruct; var i interface{} = &s; json.Unmarshal(data, i)
    -> Unmarshal decodes into s (via the &s pointer wrapped in the interface). This is how you direct json to populate a concrete value while using an interface-typed variable. [2]

Why this matters

  • Avoid "pointer to interface" unless you specifically want Unmarshal to replace the interface's concrete value with a newly allocated map/slice/number/etc. More commonly you either pass &someConcreteValue or pass &i where i is an interface{} and you want the library to choose the concrete type. [1][2]

Sources

  • encoding/json Unmarshal docs. [1]
  • Explanation and examples (Stack Overflow). [2]

Fix HydrateLog unmarshalling: pointer-to-interface causes json.Unmarshal to replace concrete struct pointers with generic types

The issue is confirmed. Passing &payload (pointer-to-interface{}) to json.Unmarshal causes it to discard the concrete struct pointers (&CreatedTransaction{}, &SavedMetadata{}, etc.) and replace the interface value with a newly-chosen generic type like map[string]interface{}. This breaks the subsequent .Elem().Interface().(LogPayload) operation.

The fix is to pass payload directly instead:

-    err := json.Unmarshal(data, &payload)
+    err := json.Unmarshal(data, payload)

This allows json.Unmarshal to unmarshal into the concrete struct pointers already stored in the interface value.

Lines affected: 451 (and same pattern applies to 451-456 as noted)

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
internal/log.go around lines 446-456: the HydrateLog unmarshalling currently
passes a pointer-to-interface (e.g. &payload) into json.Unmarshal which causes
the concrete struct pointers stored in the interface to be replaced by generic
maps; instead call json.Unmarshal with the interface value itself (payload) so
that json.Unmarshal fills the existing concrete struct pointer (e.g.
&CreatedTransaction{}, &SavedMetadata{}) and preserves the concrete type, then
continue using payload.(LogPayload) as before; update all occurrences in this
block (lines ~451-456) to pass payload (not &payload) to json.Unmarshal.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (5)
pkg/client/docs/sdks/v2/README.md (1)

246-256: Replace hard tabs with spaces in new Go examples

The new InsertSchema/GetSchema/ListSchemas Go snippets still use hard tabs in the import blocks, which markdownlint flags (MD010) and previous reviews already called out for similar sections.

Recommend normalizing these blocks to spaces-only indentation, e.g.:

-import(
-	"context"
-	"os"
+import(
+    "context"
+    "os"

Apply the same pattern to the other new examples so they’re consistent with the rest of the doc and pass markdownlint.

Also applies to: 320-327, 380-387

internal/log.go (1)

435-457: Fix HydrateLog unmarshalling: pointer-to-interface is still unsafe

The underlying issue previously flagged remains: json.Unmarshal(data, &payload) is passing a *interface{}. encoding/json will treat this as a generic interface sink, replace the value with new generic map/slice types, and ignore the concrete struct pointers (&CreatedTransaction{}, &SavedMetadata{}, &UpdatedSchema{}, etc.) you set in the switch. This makes reflect.ValueOf(payload).Elem() unsafe and can panic at runtime.

You already have the correct pattern in place: payload holds a pointer to the concrete payload type. The fix is to unmarshal into that pointer directly:

 func HydrateLog(_type LogType, data []byte) (LogPayload, error) {
@@
-    err := json.Unmarshal(data, &payload)
+    err := json.Unmarshal(data, payload)
     if err != nil {
         return nil, err
     }
 
     return reflect.ValueOf(payload).Elem().Interface().(LogPayload), nil
 }

This preserves the existing behaviour of returning the concrete value (not the pointer) while ensuring json.Unmarshal populates the right struct.

internal/controller/ledger/controller_with_traces.go (2)

47-49: Add metric.WithUnit("ms") to new schema histograms for consistency

All existing duration histograms here specify metric.WithUnit("ms"), but updateSchemaHistogram, getSchemaHistogram, and listSchemasHistogram are created without a unit. Adding the unit keeps telemetry consistent and clearer for consumers.

Also applies to: 155-166


474-495: Align tracing span name with method/metric (UpdateSchema vs UpdatedSchema)

UpdateSchema uses TraceWithMetric with operation name "UpdatedSchema", while the method and metric are named UpdateSchema/update_schema. Renaming the span to "UpdateSchema" will keep traces and metrics aligned and easier to query.

docs/api/README.md (1)

5612-5657: Reinforce: V2ChartSegment structure needs clearer documentation.

This recursive schema structure remains complex and could confuse API consumers. The past review comment correctly identified the issue. Consider adding:

  1. Clear explanation of recursion: How nested segments work (e.g., users:$userID:subaccount)
  2. Special field semantics:
    • .pattern: Regex validation for segment values
    • .rules: Custom validation rules for the segment
    • .metadata: Segment-level attributes
    • .self: Node-specific configuration
  3. Practical example: Show a complete 2-3 level nested chart with annotations

Example documentation snippet:

Chart segments define hierarchical account structures. Each key represents
a segment name (or dynamic placeholder like $userID). Special fields starting
with '.' configure behavior:
- .pattern: regex validation (e.g., "^[0-9]{16}$" ensures 16-digit IDs)
- .rules: segment-specific validation rules
- .metadata: descriptive attributes
🧹 Nitpick comments (12)
internal/chart.go (1)

38-42: ChartOfAccounts implementation is solid; consider caching compiled regex

The chart parsing and validation logic (root constraints, .self/.metadata/.rules handling, single-variable-segment per prefix, and account discovery in findAccountSchema/FindAccountSchema/ValidatePosting) is coherent and well-covered by tests.

One minor improvement: findAccountSchema recompiles the variable segment regex on every lookup via regexp.Match(variableSegment.Pattern, ...). If chart lookups become hot, consider storing a compiled *regexp.Regexp on ChartVariableSegment (e.g., a json:"-" field initialized during unmarshal) and using re.Match instead. Not urgent, but would avoid repeated compilation on the same pattern.

Also applies to: 70-162, 224-273

internal/chart_test.go (1)

359-403: Fill in test names for failing posting cases

TestPostingValidation uses empty name fields for the error-path cases, which makes failures less readable in test output.

Consider giving those cases descriptive names (e.g., "invalid source" / "invalid destination") so require.* messages clearly identify which scenario failed.

pkg/client/docs/models/components/v2chartrules.md (1)

1-7: Clarify that V2ChartRules is intentionally empty

This page shows a fields table with no rows, which can look like a generation bug. Consider adding a one‑liner under the header (or above the table) explaining that V2ChartRules currently has no top‑level fields and is reserved for future chart‑rules extensions.

test/e2e/api_schema_test.go (2)

55-137: Schema versions use identical charts despite comments about differing rules

The three inserted schemas (v1.0.0, v2.0.0, v3.0.0) all define the same Chart structure, while the comments describe them as “Basic validation”, “Stricter validation”, and “Account validation”.

If the goal is just to exercise version selection and storage, this is fine. But if you intend to validate different rule behaviors per version, you may want to slightly vary the chart definitions (e.g., patterns, required metadata, or rule presence) so the tests actually distinguish those semantics.


150-189: Consider adding an e2e for GetSchema with a non-existent version

You already cover:

  • Duplicate schema insert → SCHEMA_ALREADY_EXISTS.
  • Transaction with non-existent schema version → NOT_FOUND.

To exercise the new ErrSchemaNotFound404/NOT_FOUND path more directly, consider adding a small spec that calls GetSchema with a non-existent Version and asserts the NOT_FOUND error code. That would give you end‑to‑end coverage for both read and write flows on missing schemas.

Also applies to: 324-347

internal/api/v2/controllers_schema_list.go (1)

23-31: Consider validating the order parameter.

The current implementation silently defaults to descending order if an invalid value is provided. While this is a lenient approach, consider explicitly validating the order parameter and returning a 400 error for invalid values to provide clearer feedback to API consumers.

Apply this diff to add validation:

 		order := bunpaginate.Order(bunpaginate.OrderDesc)
 		if orderParam := r.URL.Query().Get("order"); orderParam != "" {
 			switch orderParam {
 			case "asc":
 				order = bunpaginate.Order(bunpaginate.OrderAsc)
 			case "desc":
 				order = bunpaginate.Order(bunpaginate.OrderDesc)
+			default:
+				api.BadRequest(w, common.ErrValidation, fmt.Errorf("invalid order parameter: must be 'asc' or 'desc'"))
+				return
 			}
 		}
internal/controller/ledger/log_process.go (1)

17-17: Schema selection/validation flow looks correct; consider hardening the NeedsSchema probe

The overall behaviour is sound:

  • With SchemaVersion set: FindSchema is used and newErrSchemaNotFound is surfaced on ErrNotFound, others propagate.
  • Without SchemaVersion, you only require it when OUTPUT.NeedsSchema() is true and the ledger already has at least one schema (checked via FindSchemas with PageSize: 1), which matches the “bootstrap without schema, then enforce” intent.
  • log.SchemaVersion is persisted and log.ValidateWithSchema(*schema) is invoked only when a concrete schema was resolved.

One thing to watch: var payload OUTPUT; if payload.NeedsSchema() { ... } assumes OUTPUT is a non-pointer type. If a future caller instantiates logProcessor with a pointer output (e.g. *CreatedTransaction), this will nil-deref at runtime. You might want to either (a) constrain usage to value types (and document it), or (b) refactor the “does this operation need a schema?” decision into a non-generic helper (e.g. per-operation constant) to avoid depending on the zero value of OUTPUT.

Also applies to: 73-95, 104-110

internal/controller/ledger/controller_default_test.go (1)

25-113: CreateTransaction tests correctly cover both schema and no-schema paths

The new testCreateTransaction(t, withSchema) helper and the dual invocation in TestCreateTransaction exercise:

  • The “no schema configured yet” flow (withSchema=false): FindSchemas returns an empty cursor, so missing SchemaVersion is allowed.
  • The “schema configured + explicit version” flow (withSchema=true): a minimal ledger.Schema with world/bank accounts is inserted, FindSchema("v1.0") is expected, and SchemaVersion: "v1.0" is propagated into the parameters so CreatedTransaction.ValidateWithSchema can run successfully.

This is a solid coverage of the happy paths for schema-aware transaction creation. You might later consider adding explicit negative-path tests (e.g., schema present but SchemaVersion omitted → ErrSchemaNotSpecified) once those error contracts stabilize, but it’s not blocking.

internal/log.go (2)

20-26: Log type enum updates are consistent; consider cleaning up the stale comment

Adding UpdatedSchemaLogType into the enum, JSON marshal/unmarshal, and LogTypeFromString keeps the mapping symmetric and type-safe. The panic-on-default behaviour is unchanged in spirit, even though it’s now explicit.

Minor nit: the comment // "UPDATE_CHART_OF_ACCOUNT" on UpdatedSchemaLogType no longer matches the "UPDATED_SCHEMA" string literal; updating it would avoid confusion for future readers.

Also applies to: 54-69, 71-86


267-279: Metadata/revert payloads gate on schema presence but don’t yet validate

For SavedMetadata, DeletedMetadata, and RevertedTransaction you’ve set:

  • NeedsSchema() bool { return true }
  • ValidateWithSchema(schema Schema) error { return nil }

This means that once any schema exists for a ledger, all of these operations will require a SchemaVersion to be specified (due to the NeedsSchema() check in runLog), even though they don’t currently perform any actual validation.

If the intent is purely to require callers to always pass a schemaVersion once the ledger is “schema-enabled” (even for operations that don’t consult the schema yet), this is fine. If instead you only want to gate operations that truly depend on the schema, consider setting NeedsSchema() to false for the ones that are still no-ops, or fleshing out ValidateWithSchema to perform real checks.

Also applies to: 319-331, 371-383

pkg/client/models/operations/v2listschemas.go (1)

12-61: Sort/Order enums provide strict, clear value validation

  • Sort restricts to "created_at", and Order to "asc"/"desc", with explicit errors for invalid values, which helps catch misuse early.
  • ToPointer helpers are handy for constructing optional query params.

The use of fallthrough in Order.UnmarshalJSON is slightly unconventional but functionally correct; optionally consider case "asc", "desc": for readability if you ever hand‑edit the generator.

docs/api/README.md (1)

734-734: Clarify schemaVersion parameter usage.

The schemaVersion parameter is consistently added across multiple endpoints with the description "Schema version to use for validation." However, the documentation could be enhanced with additional details:

  • What happens when not specified (falls back to default?)
  • Expected format/constraints for version strings
  • How this relates to the default schemaVersion at the bulk request level (line 734)

Consider adding a brief explanation in the "Detailed descriptions" section for at least one endpoint as a reference, or in a shared documentation section.

Also applies to: 1139-1139, 1566-1566, 1842-1842, 1907-1907

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 3476baf and 4ed3f6c.

⛔ Files ignored due to path filters (7)
  • docs/events/UpdatedSchema.json is excluded by !**/*.json
  • internal/storage/bucket/migrations/41-add-schema/notes.yaml is excluded by !**/*.yaml
  • openapi.yaml is excluded by !**/*.yaml
  • openapi/v2.yaml is excluded by !**/*.yaml
  • pkg/client/.speakeasy/gen.lock is excluded by !**/*.lock, !**/*.lock
  • pkg/client/.speakeasy/logs/naming.log is excluded by !**/*.log, !**/*.log
  • pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log is excluded by !**/*.log, !**/*.log
📒 Files selected for processing (106)
  • cmd/docs_events.go (1 hunks)
  • docs/api/README.md (22 hunks)
  • internal/README.md (37 hunks)
  • internal/api/bulking/bulker.go (6 hunks)
  • internal/api/bulking/elements.go (1 hunks)
  • internal/api/bulking/mocks_ledger_controller_test.go (3 hunks)
  • internal/api/common/errors.go (2 hunks)
  • internal/api/common/mocks_ledger_controller_test.go (3 hunks)
  • internal/api/v1/mocks_ledger_controller_test.go (3 hunks)
  • internal/api/v2/controllers_schema_insert.go (1 hunks)
  • internal/api/v2/controllers_schema_insert_test.go (1 hunks)
  • internal/api/v2/controllers_schema_list.go (1 hunks)
  • internal/api/v2/controllers_schema_list_test.go (1 hunks)
  • internal/api/v2/controllers_schema_read.go (1 hunks)
  • internal/api/v2/controllers_schema_read_test.go (1 hunks)
  • internal/api/v2/mocks_ledger_controller_test.go (3 hunks)
  • internal/api/v2/query.go (1 hunks)
  • internal/api/v2/routes.go (1 hunks)
  • internal/api/v2/views.go (1 hunks)
  • internal/bus/listener.go (1 hunks)
  • internal/chart.go (1 hunks)
  • internal/chart_test.go (1 hunks)
  • internal/controller/ledger/controller.go (2 hunks)
  • internal/controller/ledger/controller_default.go (3 hunks)
  • internal/controller/ledger/controller_default_test.go (7 hunks)
  • internal/controller/ledger/controller_generated_test.go (3 hunks)
  • internal/controller/ledger/controller_with_events.go (1 hunks)
  • internal/controller/ledger/controller_with_too_many_client_handling.go (2 hunks)
  • internal/controller/ledger/controller_with_traces.go (3 hunks)
  • internal/controller/ledger/errors.go (1 hunks)
  • internal/controller/ledger/listener.go (1 hunks)
  • internal/controller/ledger/listener_generated_test.go (1 hunks)
  • internal/controller/ledger/log_process.go (2 hunks)
  • internal/controller/ledger/log_process_test.go (4 hunks)
  • internal/controller/ledger/parameters.go (1 hunks)
  • internal/controller/ledger/store.go (2 hunks)
  • internal/controller/ledger/store_generated_test.go (3 hunks)
  • internal/errors.go (1 hunks)
  • internal/log.go (13 hunks)
  • internal/schema.go (1 hunks)
  • internal/storage/bucket/default_bucket.go (1 hunks)
  • internal/storage/bucket/migrations/35-create-compute-hash-function/up.sql (1 hunks)
  • internal/storage/bucket/migrations/41-add-schema/up.sql (1 hunks)
  • internal/storage/ledger/resource_schemas.go (1 hunks)
  • internal/storage/ledger/schema.go (1 hunks)
  • internal/storage/ledger/schema_test.go (1 hunks)
  • internal/storage/ledger/store.go (1 hunks)
  • pkg/client/README.md (1 hunks)
  • pkg/client/docs/models/components/dotself.md (1 hunks)
  • pkg/client/docs/models/components/v2bulkelementaddmetadata.md (1 hunks)
  • pkg/client/docs/models/components/v2bulkelementcreatetransaction.md (1 hunks)
  • pkg/client/docs/models/components/v2bulkelementdeletemetadata.md (1 hunks)
  • pkg/client/docs/models/components/v2bulkelementreverttransaction.md (1 hunks)
  • pkg/client/docs/models/components/v2chartrules.md (1 hunks)
  • pkg/client/docs/models/components/v2chartsegment.md (1 hunks)
  • pkg/client/docs/models/components/v2errorsenum.md (1 hunks)
  • pkg/client/docs/models/components/v2log.md (1 hunks)
  • pkg/client/docs/models/components/v2logtype.md (1 hunks)
  • pkg/client/docs/models/components/v2schema.md (1 hunks)
  • pkg/client/docs/models/components/v2schemadata.md (1 hunks)
  • pkg/client/docs/models/components/v2schemaresponse.md (1 hunks)
  • pkg/client/docs/models/components/v2schemascursor.md (1 hunks)
  • pkg/client/docs/models/components/v2schemascursorresponse.md (1 hunks)
  • pkg/client/docs/models/operations/order.md (1 hunks)
  • pkg/client/docs/models/operations/queryparamorder.md (1 hunks)
  • pkg/client/docs/models/operations/sort.md (1 hunks)
  • pkg/client/docs/models/operations/v2addmetadataontransactionrequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2addmetadatatoaccountrequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2createbulkrequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2createtransactionrequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2getschemarequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2getschemaresponse.md (1 hunks)
  • pkg/client/docs/models/operations/v2insertschemarequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2insertschemaresponse.md (1 hunks)
  • pkg/client/docs/models/operations/v2listschemasrequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2listschemasresponse.md (1 hunks)
  • pkg/client/docs/models/operations/v2listtransactionsrequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2reverttransactionrequest.md (1 hunks)
  • pkg/client/docs/sdks/v2/README.md (7 hunks)
  • pkg/client/models/components/v2bulkelementaddmetadata.go (2 hunks)
  • pkg/client/models/components/v2bulkelementcreatetransaction.go (2 hunks)
  • pkg/client/models/components/v2bulkelementdeletemetadata.go (2 hunks)
  • pkg/client/models/components/v2bulkelementreverttransaction.go (2 hunks)
  • pkg/client/models/components/v2chartrules.go (1 hunks)
  • pkg/client/models/components/v2chartsegment.go (1 hunks)
  • pkg/client/models/components/v2errorsenum.go (2 hunks)
  • pkg/client/models/components/v2log.go (4 hunks)
  • pkg/client/models/components/v2schema.go (1 hunks)
  • pkg/client/models/components/v2schemadata.go (1 hunks)
  • pkg/client/models/components/v2schemaresponse.go (1 hunks)
  • pkg/client/models/components/v2schemascursor.go (1 hunks)
  • pkg/client/models/components/v2schemascursorresponse.go (1 hunks)
  • pkg/client/models/operations/v2addmetadataontransaction.go (2 hunks)
  • pkg/client/models/operations/v2addmetadatatoaccount.go (2 hunks)
  • pkg/client/models/operations/v2createbulk.go (2 hunks)
  • pkg/client/models/operations/v2createtransaction.go (2 hunks)
  • pkg/client/models/operations/v2getschema.go (1 hunks)
  • pkg/client/models/operations/v2insertschema.go (1 hunks)
  • pkg/client/models/operations/v2listschemas.go (1 hunks)
  • pkg/client/models/operations/v2listtransactions.go (3 hunks)
  • pkg/client/models/operations/v2reverttransaction.go (2 hunks)
  • pkg/client/v2.go (1 hunks)
  • pkg/events/events.go (1 hunks)
  • pkg/events/message.go (1 hunks)
  • test/e2e/api_schema_test.go (1 hunks)
  • test/e2e/api_transactions_list_test.go (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • pkg/client/docs/models/operations/v2listschemasrequest.md
🚧 Files skipped from review as they are similar to previous changes (67)
  • internal/api/bulking/elements.go
  • internal/controller/ledger/listener.go
  • pkg/client/models/operations/v2addmetadataontransaction.go
  • pkg/client/docs/models/components/v2schemaresponse.md
  • pkg/client/docs/models/components/v2logtype.md
  • pkg/client/models/components/v2schema.go
  • pkg/client/models/operations/v2createtransaction.go
  • pkg/client/docs/models/components/v2schemadata.md
  • pkg/events/message.go
  • internal/controller/ledger/controller.go
  • pkg/client/docs/models/components/v2bulkelementcreatetransaction.md
  • internal/storage/ledger/schema_test.go
  • internal/storage/bucket/migrations/35-create-compute-hash-function/up.sql
  • internal/controller/ledger/parameters.go
  • pkg/client/models/operations/v2createbulk.go
  • pkg/client/models/components/v2bulkelementaddmetadata.go
  • pkg/client/docs/models/operations/v2insertschemaresponse.md
  • pkg/client/docs/models/operations/v2addmetadatatoaccountrequest.md
  • pkg/client/docs/models/components/v2errorsenum.md
  • pkg/client/docs/models/operations/v2listtransactionsrequest.md
  • pkg/client/docs/models/operations/v2listschemasresponse.md
  • internal/schema.go
  • internal/storage/bucket/migrations/41-add-schema/up.sql
  • internal/bus/listener.go
  • pkg/events/events.go
  • pkg/client/docs/models/operations/sort.md
  • internal/controller/ledger/controller_with_too_many_client_handling.go
  • pkg/client/docs/models/components/v2bulkelementreverttransaction.md
  • pkg/client/docs/models/components/v2bulkelementaddmetadata.md
  • pkg/client/models/components/v2bulkelementreverttransaction.go
  • internal/api/v2/controllers_schema_insert_test.go
  • test/e2e/api_transactions_list_test.go
  • pkg/client/models/components/v2schemadata.go
  • cmd/docs_events.go
  • pkg/client/docs/models/operations/v2getschemaresponse.md
  • pkg/client/README.md
  • internal/storage/ledger/resource_schemas.go
  • pkg/client/models/components/v2schemascursor.go
  • pkg/client/models/components/v2schemascursorresponse.go
  • pkg/client/docs/models/operations/v2getschemarequest.md
  • internal/api/v2/routes.go
  • pkg/client/docs/models/components/v2log.md
  • internal/api/v2/views.go
  • internal/errors.go
  • internal/api/v2/query.go
  • pkg/client/models/operations/v2addmetadatatoaccount.go
  • pkg/client/docs/models/components/v2schema.md
  • pkg/client/docs/models/operations/v2createbulkrequest.md
  • internal/api/common/mocks_ledger_controller_test.go
  • pkg/client/docs/models/operations/queryparamorder.md
  • pkg/client/docs/models/operations/v2addmetadataontransactionrequest.md
  • pkg/client/models/components/v2bulkelementcreatetransaction.go
  • internal/storage/ledger/store.go
  • pkg/client/docs/models/components/dotself.md
  • internal/api/v2/controllers_schema_insert.go
  • pkg/client/models/operations/v2listtransactions.go
  • pkg/client/models/components/v2errorsenum.go
  • pkg/client/docs/models/operations/v2insertschemarequest.md
  • internal/controller/ledger/controller_default.go
  • pkg/client/docs/models/components/v2schemascursorresponse.md
  • internal/controller/ledger/errors.go
  • internal/controller/ledger/store.go
  • pkg/client/docs/models/components/v2schemascursor.md
  • pkg/client/docs/models/operations/v2reverttransactionrequest.md
  • internal/storage/bucket/default_bucket.go
  • internal/controller/ledger/controller_with_events.go
  • internal/controller/ledger/log_process_test.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-04-29T11:24:28.923Z
Learnt from: gfyrag
Repo: formancehq/ledger PR: 892
File: internal/controller/ledger/controller_default.go:196-196
Timestamp: 2025-04-29T11:24:28.923Z
Learning: In the ledger Import function, it's critical to maintain proper log ID tracking by updating lastLogID with the current log.ID after each processed log, rather than setting it to nil. This ensures the system can properly validate the ordering of logs and prevent duplicate or out-of-order processing, which is essential for maintaining data integrity in the ledger.

Applied to files:

  • internal/README.md
🧬 Code graph analysis (27)
internal/api/v2/controllers_schema_read.go (1)
internal/api/common/errors.go (1)
  • HandleCommonErrors (38-45)
pkg/client/models/operations/v2reverttransaction.go (1)
pkg/client/models/components/v2reverttransactionrequest.go (1)
  • V2RevertTransactionRequest (5-7)
internal/api/v2/controllers_schema_list.go (4)
internal/api/common/pagination.go (1)
  • PaginationConfig (3-6)
pkg/client/models/operations/v2listschemas.go (3)
  • Order (37-37)
  • OrderDesc (41-41)
  • OrderAsc (40-40)
internal/api/common/errors.go (2)
  • ErrValidation (20-20)
  • HandleCommonPaginationErrors (65-74)
internal/schema.go (1)
  • Schema (15-21)
internal/chart_test.go (2)
internal/chart.go (5)
  • ChartOfAccounts (36-36)
  • ChartVariableSegment (23-28)
  • ChartSegment (17-21)
  • ChartAccount (12-15)
  • ChartAccountRules (10-10)
internal/errors.go (1)
  • ErrInvalidAccount (102-105)
internal/log.go (5)
internal/storage/ledger/logs.go (1)
  • Log (20-26)
internal/schema.go (1)
  • Schema (15-21)
internal/transaction.go (2)
  • Transaction (37-51)
  • Transaction (53-60)
internal/posting.go (1)
  • Postings (35-35)
pkg/events/message.go (4)
  • SavedMetadata (27-32)
  • DeletedMetadata (60-65)
  • RevertedTransaction (44-48)
  • UpdatedSchema (77-80)
pkg/client/models/components/v2bulkelementdeletemetadata.go (2)
pkg/generate/generator.go (1)
  • Action (27-29)
pkg/client/models/components/v2bulkelementaddmetadata.go (1)
  • Data (5-9)
internal/api/v2/controllers_schema_read_test.go (3)
internal/schema.go (2)
  • Schema (15-21)
  • SchemaData (11-13)
internal/controller/ledger/errors.go (1)
  • ErrNotFound (14-14)
internal/api/v2/routes.go (1)
  • NewRouter (28-135)
pkg/client/models/operations/v2insertschema.go (1)
pkg/client/models/components/v2schemadata.go (1)
  • V2SchemaData (6-9)
internal/api/v1/mocks_ledger_controller_test.go (6)
internal/api/v2/mocks_ledger_controller_test.go (2)
  • LedgerController (26-30)
  • LedgerControllerMockRecorder (33-35)
internal/schema.go (1)
  • Schema (15-21)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (26-29)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (135-138)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/log.go (1)
  • UpdatedSchema (418-420)
internal/controller/ledger/store_generated_test.go (2)
internal/schema.go (1)
  • Schema (15-21)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (26-29)
internal/api/common/errors.go (1)
internal/controller/ledger/errors.go (5)
  • ErrInvalidIdempotencyInput (221-225)
  • ErrSchemaValidationError (268-271)
  • ErrNotFound (14-14)
  • ErrSchemaNotSpecified (289-289)
  • ErrSchemaNotFound (249-251)
pkg/client/models/operations/v2getschema.go (1)
pkg/client/models/components/v2schemaresponse.go (1)
  • V2SchemaResponse (5-8)
internal/controller/ledger/controller_default_test.go (4)
internal/schema.go (2)
  • Schema (15-21)
  • SchemaData (11-13)
internal/chart.go (2)
  • ChartOfAccounts (36-36)
  • ChartAccount (12-15)
internal/controller/ledger/controller.go (2)
  • CreateTransaction (102-106)
  • RunScript (91-91)
internal/machine/vm/run.go (1)
  • RunScript (14-19)
internal/controller/ledger/log_process.go (4)
internal/schema.go (1)
  • Schema (15-21)
internal/controller/ledger/errors.go (1)
  • ErrNotFound (14-14)
internal/storage/common/pagination.go (1)
  • InitialPaginatedQuery (10-15)
internal/log.go (2)
  • NewLog (194-199)
  • ComputeIdempotencyHash (459-468)
pkg/client/models/components/v2chartsegment.go (2)
pkg/client/models/components/v2chartrules.go (1)
  • V2ChartRules (5-6)
pkg/client/internal/utils/json.go (2)
  • MarshalJSON (21-115)
  • UnmarshalJSON (117-251)
internal/controller/ledger/controller_generated_test.go (5)
internal/schema.go (1)
  • Schema (15-21)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (26-29)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (135-138)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/log.go (1)
  • UpdatedSchema (418-420)
internal/controller/ledger/controller_with_traces.go (6)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (135-138)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/log.go (1)
  • UpdatedSchema (418-420)
internal/tracing/tracing.go (1)
  • TraceWithMetric (101-129)
internal/schema.go (1)
  • Schema (15-21)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (26-29)
internal/storage/ledger/schema.go (3)
internal/controller/ledger/store.go (1)
  • Store (29-66)
internal/storage/ledger/store.go (1)
  • Store (25-50)
internal/schema.go (1)
  • Schema (15-21)
pkg/client/models/operations/v2listschemas.go (2)
pkg/client/internal/utils/json.go (2)
  • UnmarshalJSON (117-251)
  • MarshalJSON (21-115)
pkg/client/models/components/v2schemascursorresponse.go (1)
  • V2SchemasCursorResponse (5-7)
internal/api/v2/mocks_ledger_controller_test.go (4)
internal/schema.go (1)
  • Schema (15-21)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (26-29)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/log.go (2)
  • Log (89-102)
  • UpdatedSchema (418-420)
internal/api/v2/controllers_schema_list_test.go (5)
internal/storage/common/pagination.go (2)
  • PaginatedQuery (26-29)
  • InitialPaginatedQuery (10-15)
internal/schema.go (2)
  • Schema (15-21)
  • SchemaData (11-13)
cmd/root.go (1)
  • Version (25-25)
internal/storage/common/resource.go (1)
  • ResourceQuery (410-416)
internal/api/v2/routes.go (1)
  • NewRouter (28-135)
pkg/client/models/components/v2schemaresponse.go (1)
pkg/client/models/components/v2schema.go (1)
  • V2Schema (11-18)
internal/api/bulking/mocks_ledger_controller_test.go (4)
internal/schema.go (1)
  • Schema (15-21)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (26-29)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/log.go (1)
  • UpdatedSchema (418-420)
internal/chart.go (3)
internal/account.go (1)
  • Account (16-26)
pkg/client/internal/utils/json.go (2)
  • UnmarshalJSON (117-251)
  • MarshalJSON (21-115)
internal/errors.go (1)
  • ErrInvalidAccount (102-105)
test/e2e/api_schema_test.go (11)
test/e2e/suite_test.go (1)
  • UseTemplatedDatabase (207-209)
pkg/client/models/operations/v2listschemas.go (1)
  • V2ListSchemasRequest (63-74)
pkg/client/models/components/v2schemascursorresponse.go (1)
  • V2SchemasCursorResponse (5-7)
pkg/client/models/operations/v2insertschema.go (1)
  • V2InsertSchemaRequest (9-15)
pkg/client/models/components/v2schemadata.go (1)
  • V2SchemaData (6-9)
pkg/client/models/components/v2chartsegment.go (1)
  • V2ChartSegment (13-19)
pkg/client/models/components/v2errorsenum.go (4)
  • V2ErrorsEnumSchemaAlreadyExists (30-30)
  • V2ErrorsEnumValidation (15-15)
  • V2ErrorsEnumSchemaNotSpecified (31-31)
  • V2ErrorsEnumNotFound (19-19)
pkg/client/models/operations/v2createtransaction.go (1)
  • V2CreateTransactionRequest (9-27)
pkg/client/models/components/v2posttransaction.go (1)
  • V2PostTransaction (58-68)
pkg/client/models/components/v2posting.go (1)
  • V2Posting (10-15)
pkg/client/models/components/v2log.go (1)
  • V2Log (48-56)
internal/controller/ledger/listener_generated_test.go (3)
internal/log.go (1)
  • UpdatedSchema (418-420)
pkg/events/message.go (1)
  • UpdatedSchema (77-80)
internal/schema.go (1)
  • Schema (15-21)
pkg/client/v2.go (15)
pkg/client/models/operations/v2insertschema.go (2)
  • V2InsertSchemaRequest (9-15)
  • V2InsertSchemaResponse (38-40)
pkg/client/models/operations/options.go (2)
  • Option (41-41)
  • Options (32-39)
pkg/client/internal/utils/utils.go (3)
  • ReplaceParameters (51-60)
  • ConsumeRawBody (350-360)
  • UnmarshalJsonFromResponseBody (39-49)
pkg/client/internal/utils/pathparams.go (1)
  • GenerateURL (19-38)
pkg/client/internal/hooks/hooks.go (1)
  • HookContext (27-35)
pkg/client/internal/config/sdkconfiguration.go (1)
  • SDKConfiguration (16-25)
pkg/client/internal/utils/requestbody.go (1)
  • SerializeRequestBody (31-42)
pkg/client/internal/utils/security.go (1)
  • PopulateSecurity (27-92)
pkg/client/internal/utils/retries.go (3)
  • RetryConfig (23-23)
  • Retries (25-28)
  • Retry (30-104)
pkg/client/models/components/v2errorresponse.go (1)
  • V2ErrorResponse (5-9)
pkg/client/models/operations/v2getschema.go (2)
  • V2GetSchemaRequest (9-14)
  • V2GetSchemaResponse (30-34)
pkg/client/models/components/v2schemaresponse.go (1)
  • V2SchemaResponse (5-8)
pkg/client/models/operations/v2listschemas.go (2)
  • V2ListSchemasRequest (63-74)
  • V2ListSchemasResponse (122-126)
pkg/client/internal/utils/queryparams.go (1)
  • PopulateQueryParams (20-43)
pkg/client/models/components/v2schemascursorresponse.go (1)
  • V2SchemasCursorResponse (5-7)
🪛 markdownlint-cli2 (0.18.1)
pkg/client/docs/sdks/v2/README.md

250-250: Hard tabs
Column: 1

(MD010, no-hard-tabs)


251-251: Hard tabs
Column: 1

(MD010, no-hard-tabs)


252-252: Hard tabs
Column: 1

(MD010, no-hard-tabs)


253-253: Hard tabs
Column: 1

(MD010, no-hard-tabs)


254-254: Hard tabs
Column: 1

(MD010, no-hard-tabs)


255-255: Hard tabs
Column: 1

(MD010, no-hard-tabs)


321-321: Hard tabs
Column: 1

(MD010, no-hard-tabs)


322-322: Hard tabs
Column: 1

(MD010, no-hard-tabs)


323-323: Hard tabs
Column: 1

(MD010, no-hard-tabs)


324-324: Hard tabs
Column: 1

(MD010, no-hard-tabs)


325-325: Hard tabs
Column: 1

(MD010, no-hard-tabs)


326-326: Hard tabs
Column: 1

(MD010, no-hard-tabs)


381-381: Hard tabs
Column: 1

(MD010, no-hard-tabs)


382-382: Hard tabs
Column: 1

(MD010, no-hard-tabs)


383-383: Hard tabs
Column: 1

(MD010, no-hard-tabs)


384-384: Hard tabs
Column: 1

(MD010, no-hard-tabs)


385-385: Hard tabs
Column: 1

(MD010, no-hard-tabs)


386-386: Hard tabs
Column: 1

(MD010, no-hard-tabs)

🔇 Additional comments (36)
pkg/client/models/operations/v2reverttransaction.go (2)

22-23: LGTM!

The new SchemaVersion field is correctly implemented as an optional query parameter, consistent with other optional fields in the request struct. The pointer type and query parameter tags follow the established pattern.

Note: This is generated code (line 1), so any issues with the field definition would need to be addressed in the generator specification or OpenAPI spec.


73-78: LGTM!

The GetSchemaVersion accessor method correctly follows the established pattern for getter methods in this file. It properly handles the nil receiver case and returns the appropriate type.

pkg/client/docs/models/components/v2bulkelementdeletemetadata.md (1)

10-10: SchemaVersion field documentation is well-structured.

The new SchemaVersion field is appropriately documented with clear description ("Schema version to use for validation"), correct optional status, and a realistic example format. The addition aligns with the PR's schema management objectives.

pkg/client/docs/models/operations/order.md (1)

3-11: Order enum docs look consistent

OrderAsc/OrderDesc mapping to asc/desc is clear and matches expected sort semantics; nothing else to change here.

internal/api/bulking/mocks_ledger_controller_test.go (1)

214-227: Schema-related bulking mocks are correctly wired

The added GetSchema, ListSchemas, and UpdateSchema mocks (and recorders) follow the existing gomock pattern and match the controller signatures, so bulking tests can now exercise schema paths without extra changes.

Also applies to: 347-360, 454-468

internal/api/v1/mocks_ledger_controller_test.go (1)

214-227: v1 LedgerController mocks updated consistently for schema APIs

GetSchema, ListSchemas, and UpdateSchema are added with the expected signatures and recorder methods, mirroring other mock sets; this keeps v1 tests in sync with the controller interface.

Also applies to: 347-360, 454-468

pkg/client/models/operations/v2insertschema.go (1)

9-47: V2InsertSchema request/response types look correct

Path params and JSON body wiring for V2InsertSchemaRequest, plus the nil-safe getters and V2InsertSchemaResponse wrapper, are consistent with the rest of the client operations.

internal/README.md (1)

19-41: Updated internal API docs correctly expose chart/schema features

The regenerated gomarkdoc output now documents the new chart-of-accounts and schema concepts (ValidateSegment, ChartOfAccounts and friends, Schema/SchemaData, UpdatedSchema, SchemaVersion on Log, and related errors). This aligns with the new public surface added in the Go packages and keeps the internal package docs in sync.

Also applies to: 212-241, 252-257, 318-323, 418-447, 484-527, 579-623, 711-737, 798-824, 1008-1113, 1524-1557, 1787-1821

pkg/client/models/components/v2chartrules.go (1)

1-6: Generated placeholder type looks correct

V2ChartRules as an empty generated struct cleanly satisfies the current usage (e.g., DotRules: &components.V2ChartRules{} in tests) without adding behavior. No changes needed here.

internal/api/v2/controllers_schema_read.go (1)

14-30: LGTM!

The handler implementation is clean and follows established patterns for v2 API endpoints. Error handling appropriately distinguishes not-found errors (404) from other errors, and the success path correctly returns the schema.

internal/api/v2/controllers_schema_list_test.go (1)

24-170: LGTM!

The test provides comprehensive coverage of the ListSchemas handler, including nominal cases, pagination, sorting, error handling, and input validation. The test structure is clean with parallel execution and appropriate use of gomock expectations.

internal/api/v2/controllers_schema_read_test.go (1)

21-102: LGTM!

The test provides good coverage of the readSchema handler with appropriate scenarios: successful retrieval, not found error, and backend errors. The gomock expectations and assertions are correctly structured.

pkg/client/models/components/v2bulkelementdeletemetadata.go (1)

32-59: LGTM!

The addition of the SchemaVersion field and its getter method is consistent with the broader schema versioning feature. The implementation follows established patterns for optional fields in bulk operations.

pkg/client/models/components/v2schemaresponse.go (1)

5-15: LGTM!

The response wrapper type is correctly implemented and follows standard patterns for API response models. The defensive nil-check in the getter is appropriate.

internal/controller/ledger/listener_generated_test.go (1)

91-101: LGTM!

The generated mock methods for UpdatedSchema are correctly implemented and follow the established patterns for other listener methods in the codebase.

internal/api/v2/controllers_schema_list.go (1)

45-47: renderSchema function is properly defined and accessible.

The renderSchema function is defined at internal/api/v2/views.go:146 with the correct signature:

func renderSchema(r *http.Request, v ledger.Schema) any {
	return v
}

The cursor rendering implementation is correct.

internal/api/bulking/bulker.go (1)

143-267: BulkElement SchemaVersion field verified.

The BulkElement struct includes the SchemaVersion field, confirming that all six bulk actions can correctly propagate SchemaVersion across the processElement method implementation.

pkg/client/models/operations/v2getschema.go (1)

1-48: GetSchema operation models look consistent and safe

The request/response structs and their getters follow the established Speakeasy patterns (path params, HTTPMeta, nil‑safe accessors). No issues from a correctness or ergonomics standpoint.

pkg/client/models/components/v2log.go (1)

16-21: Updated log types/schemaVersion exposure are well-integrated

The addition of V2LogTypeUpdatedSchema, the corresponding Unmarshal case, and the optional SchemaVersion field/getter on V2Log all look correct and consistent with existing patterns. This should be backwards compatible while exposing the new schema-aware log information to clients.

Also applies to: 31-56, 104-109

internal/controller/ledger/controller_default_test.go (1)

137-140: Additional FindSchemas expectations align revert/metadata tests with schema-aware log processing

The extra FindSchemas expectations in TestRevertTransaction, TestSaveTransactionMetadata, and TestDeleteTransactionMetadata correctly reflect the new NeedsSchema() behaviour for these payload types. Returning an empty cursor models the “no schemas yet” state, so these tests continue to validate the pre-schema behaviour while still covering the new call pattern.

Also applies to: 197-200, 247-250

internal/log.go (3)

88-102: SchemaVersion propagation and hashing look correct

Storing SchemaVersion on Log and including it in ComputeHash via a field with json:"schemaVersion,omitempty" is a good way to bind the chain hash to the schema used for validation without perturbing hashes when no schema is set. The dedicated Log.ValidateWithSchema(schema) helper cleanly delegates to the payload and keeps higher-level code simple.

No issues here.

Also applies to: 146-183, 190-192


201-205: CreatedTransaction schema validation is wired as expected

Extending LogPayload with NeedsSchema() / ValidateWithSchema(Schema) and implementing:

  • CreatedTransaction.NeedsSchema() bool { return true }
  • CreatedTransaction.ValidateWithSchema that iterates all postings and calls schema.Chart.ValidatePosting(posting)

provides a clear enforcement point for chart-of-accounts rules. This integrates well with the controller’s new pre-insert validation step.

Looks good.

Also applies to: 213-216, 218-230


418-434: UpdatedSchema payload type is correctly integrated

The UpdatedSchema payload with a Schema field, NeedsSchema() == false, no-op ValidateWithSchema, and Type() == UpdatedSchemaLogType fits well with the existing LogPayload pattern and matches the events model (pkg/events/message.go). It’s reasonable that this payload doesn’t itself require validation against another schema.

internal/storage/ledger/schema.go (1)

13-40: Storage schema methods look correct and aligned with existing patterns

InsertSchema, FindSchema, and FindSchemas correctly scope by ledger, use the shared pagination resource, and normalize errors via postgres.ResolveError. No issues spotted.

internal/controller/ledger/controller_with_traces.go (1)

474-539: Schema controller methods with traces follow existing patterns

UpdateSchema, GetSchema, and ListSchemas mirror the established tracing/metric pattern used elsewhere in this type: they wrap the underlying controller, capture latency via histograms, and propagate results/errors correctly. No additional issues spotted.

internal/api/v2/mocks_ledger_controller_test.go (1)

214-227: New schema-related gomock methods look correct

GetSchema, ListSchemas, and UpdateSchema mocks follow the usual gomock pattern and their signatures align with the controller interface types (*ledger.Schema, *bunpaginate.Cursor[ledger.Schema], Parameters[UpdateSchema]). Nothing to change here.

Also applies to: 347-360, 454-468

pkg/client/v2.go (3)

656-849: InsertSchema client method aligns with existing V2 patterns

  • URL template /v2/{ledger}/schema/{version} matches V2InsertSchemaRequest path params and expected endpoint.
  • Request body is correctly taken from the V2SchemaData field via SerializeRequestBody, required (non‑optional) and JSON‑encoded.
  • Retry, timeout, hooks, security, headers, and 204/no‑body success handling mirror CreateLedger and similar write operations.

No issues from a client or API‑surface perspective.


851-1057: GetSchema method correctly wires read endpoint and response model

  • URL /v2/{ledger}/schema/{version} and GET verb are consistent with V2GetSchemaRequest.
  • On 200 + application/json, the body is deserialized into components.V2SchemaResponse and attached to V2GetSchemaResponse, matching the provided component type.
  • Non‑200 responses use the standard sdkerrors.V2ErrorResponse path, with hooks and retry behavior identical to other read methods like GetLedger.

Looks correct and consistent.


1059-1267: ListSchemas method correctly supports pagination and sorting

  • URL /v2/{ledger}/schema and GET verb match V2ListSchemasRequest (ledger in path, cursor/pageSize/sort/order as query params).
  • PopulateQueryParams is invoked so cursor, sort, and order are honored, with defaults driven by struct tags.
  • Successful 200 JSON responses are decoded into components.V2SchemasCursorResponse and attached to V2ListSchemasResponse.
  • Retry, timeout, hooks, and error handling follow the established V2 list‑style methods (e.g., ListAccounts, ListLogs).

No functional issues spotted.

internal/controller/ledger/store_generated_test.go (1)

15-24: Schema-related Store mocks are consistent with the updated Store interface

  • FindSchema(ctx context.Context, version string) (*ledger.Schema, error) and InsertSchema(ctx context.Context, data *ledger.Schema) error match the described Store additions and correctly type‑assert results in the gomock plumbing.
  • FindSchemas(ctx context.Context, query common.PaginatedQuery[any]) (*bunpaginate.Cursor[ledger.Schema], error) uses the right generic cursor type and is wired up to gomock in the standard pattern.
  • The added bunpaginate import is used only for the cursor return type and does not introduce unused imports.

These mocks should integrate cleanly with existing controller tests.

Also applies to: 152-180, 226-238

pkg/client/models/operations/v2listschemas.go (1)

63-140: V2ListSchemasRequest/Response models are aligned with client and pagination patterns

  • V2ListSchemasRequest fields and query/path tags (ledger, cursor, pageSize, sort, order) match the client method and expected API surface, including sensible defaults via default:"..." tags.
  • Marshal/Unmarshal delegate to utils.MarshalJSON/utils.UnmarshalJSON, consistent with other generated operation models and enabling default handling and additional future tags.
  • Getter methods are nil‑safe and follow the same pattern as other SDK types.
  • V2ListSchemasResponse wraps HTTPMeta and V2SchemasCursorResponse in the standard way used across V2 list endpoints.

Everything here looks coherent with existing SDK conventions.

pkg/client/models/components/v2chartsegment.go (1)

1-65: V2ChartSegment JSON mapping correctly models dotted chart-of-accounts segments

  • JSON tags like json:".self,omitempty" and json:".pattern,omitempty" will serialize to the expected dotted keys, and are compatible with the shared utils.MarshalJSON/UnmarshalJSON logic.
  • AdditionalProperties map[string]V2ChartSegment with additionalProperties:"true" correctly captures arbitrary nested segments while keeping the known dotted keys first‑class.
  • The empty DotSelf marker and nil‑safe getters make it easy to detect presence of .self and other fields.

This component looks well‑aligned with the rest of the schema/chart modelling.

internal/controller/ledger/controller_generated_test.go (1)

213-467: LGTM! Auto-generated mock methods are correct.

The three new mock methods (GetSchema, ListSchemas, UpdateSchema) follow the established pattern and correctly match the interface signatures. Type references align with the relevant code snippets (ledger.Schema, Parameters[UpdateSchema], ledger.UpdatedSchema).

docs/api/README.md (3)

318-508: LGTM! Schema endpoint documentation is comprehensive.

The three new schema endpoints (insert/update, get by version, and list) are well-documented with complete parameter descriptions, example requests/responses, status codes, and authentication requirements. The structure follows existing API documentation patterns.


4028-4029: LGTM! V2Log schema version integration is consistent.

The schemaVersion field is properly added to the V2Log structure with clear documentation ("Schema version used for validation"), and the UPDATED_SCHEMA log type enum value is appropriately included. The example responses at lines 2219-2220 and 3506-3507 consistently demonstrate the new field.

Also applies to: 4043-4043, 4053-4053


4553-4554: LGTM! Error codes and bulk element schema integration are consistent.

The new error codes (SCHEMA_ALREADY_EXISTS, SCHEMA_NOT_SPECIFIED) appropriately extend the V2ErrorsEnum for schema validation scenarios. The schemaVersion field is systematically added to V2BaseBulkElement and properly inherited by all bulk element variants (CreateTransaction, AddMetadata, RevertTransaction, DeleteMetadata) with consistent descriptions.

Also applies to: 4667-5017

Comment on lines +27 to 29
ErrSchemaAlreadyExists = "SCHEMA_ALREADY_EXISTS"
ErrSchemaNotSpecified = "SCHEMA_NOT_SPECIFIED"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Wire ErrSchemaAlreadyExists into HandleCommonWriteErrors

You’ve introduced the ErrSchemaAlreadyExists error code but HandleCommonWriteErrors never maps ledgercontroller.ErrSchemaAlreadyExists to it. As written, a duplicate schema insert will most likely fall through to HandleCommonErrors and surface as a 500/internal error instead of a 409 with SCHEMA_ALREADY_EXISTS (which the e2e test expects).

Consider adding an explicit branch:

case errors.Is(err, ledgercontroller.ErrSchemaAlreadyExists{}):
	api.WriteErrorResponse(w, http.StatusConflict, ErrSchemaAlreadyExists, err)

This keeps the HTTP semantics aligned with V2ErrorsEnumSchemaAlreadyExists and the new e2e “duplicate schema” test.

Also applies to: 51-59

🤖 Prompt for AI Agents
internal/api/common/errors.go around lines 27-29 (and also apply the same change
in the HandleCommonWriteErrors branch at lines ~51-59): the new
ErrSchemaAlreadyExists constant is not being returned by HandleCommonWriteErrors
when ledgercontroller.ErrSchemaAlreadyExists occurs, so duplicate schema inserts
fall through as 500s; add an explicit branch in HandleCommonWriteErrors that
checks errors.Is(err, ledgercontroller.ErrSchemaAlreadyExists) and calls
api.WriteErrorResponse(w, http.StatusConflict, ErrSchemaAlreadyExists, err) so
the handler returns a 409 with the SCHEMA_ALREADY_EXISTS code.

Comment on lines 9 to 10
| `Ik` | **string* | :heavy_minus_sign: | N/A | |
| `SchemaVersion` | **string* | :heavy_minus_sign: | Schema version to use for validation | v1.0.0 |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix malformed Markdown syntax on type column.

Lines 9–10 contain malformed type markup **string* (unmatched delimiters). This should be *string* to match the formatting on line 8 and render correctly.

Apply this diff to fix the syntax errors:

-| `Ik`                                                                                                      | **string*                                                                                                 | :heavy_minus_sign:                                                                                        | N/A                                                                                                       |                                                                                                           |
+| `Ik`                                                                                                      | *string*                                                                                                  | :heavy_minus_sign:                                                                                        | N/A                                                                                                       |                                                                                                           |
-| `SchemaVersion`                                                                                           | **string*                                                                                                 | :heavy_minus_sign:                                                                                        | Schema version to use for validation                                                                      | v1.0.0                                                                                                    |
+| `SchemaVersion`                                                                                           | *string*                                                                                                  | :heavy_minus_sign:                                                                                        | Schema version to use for validation                                                                      | v1.0.0                                                                                                    |
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| `Ik` | **string* | :heavy_minus_sign: | N/A | |
| `SchemaVersion` | **string* | :heavy_minus_sign: | Schema version to use for validation | v1.0.0 |
| `Ik` | *string* | :heavy_minus_sign: | N/A | |
| `SchemaVersion` | *string* | :heavy_minus_sign: | Schema version to use for validation | v1.0.0 |
🤖 Prompt for AI Agents
In pkg/client/docs/models/components/v2bulkelementdeletemetadata.md around lines
9 to 10, the type column contains malformed markdown `**string*`; replace each
`**string*` with `*string*` (i.e., single asterisks surrounding string) on both
the `Ik` and `SchemaVersion` rows so the emphasis markup matches the other lines
and the table renders correctly, preserving existing spacing and table
alignment.

Comment on lines 8 to 14
| Field | Type | Required | Description | Example |
| --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- |
| `DotSelf` | [*components.DotSelf](../../models/components/dotself.md) | :heavy_minus_sign: | N/A | |
| `DotPattern` | **string* | :heavy_minus_sign: | N/A | |
| `DotRules` | [*components.V2ChartRules](../../models/components/v2chartrules.md) | :heavy_minus_sign: | N/A | |
| `DotMetadata` | map[string]*string* | :heavy_minus_sign: | N/A | |
| `AdditionalProperties` | map[string][components.V2ChartSegment](../../models/components/v2chartsegment.md) | :heavy_minus_sign: | N/A | {<br/>"users": {<br/>"$userID": {<br/>".pattern": "^[0-9]{16}$"<br/>}<br/>}<br/>} | No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix type formatting for DotPattern and DotMetadata

The types in the table don’t quite match the Go model and have broken Markdown:

  • DotPattern shows as **string* instead of something like *string* (and renders oddly).
  • DotMetadata shows as map[string]*string*, but the Go type is map[string]string, and the trailing * also breaks formatting.

Consider updating these rows to something along the lines of:

| `DotPattern`  | *string*           | :heavy_minus_sign: | N/A |   |
| `DotMetadata` | map[string]string  | :heavy_minus_sign: | N/A |   |

so the docs align with V2ChartSegment and render cleanly.

🤖 Prompt for AI Agents
In pkg/client/docs/models/components/v2chartsegment.md around lines 8 to 14, the
table row types for DotPattern and DotMetadata are misformatted and don't match
the Go model; update the `DotPattern` cell to use italicized string markup
(*string*) and change the `DotMetadata` cell to the correct Go type
`map[string]string` (remove the extraneous asterisks), ensuring the Markdown
pipe-separated table stays valid and renders correctly.

| `DryRun` | **bool* | :heavy_minus_sign: | Set the dryRun mode. dry run mode doesn't add the logs to the database or publish a message to the message broker. | true |
| `IdempotencyKey` | **string* | :heavy_minus_sign: | Use an idempotency key | |
| ~~`Force`~~ | **bool* | :heavy_minus_sign: | : warning: ** DEPRECATED **: This will be removed in a future release, please migrate away from it as soon as possible.<br/><br/>Disable balance checks when passing postings | true |
| `SchemaVersion` | **string* | :heavy_minus_sign: | Schema version to use for validation | v1.0.0 |
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix type formatting inconsistency.

The type notation **string* on line 12 is inconsistent with the notation used for required fields elsewhere in the table (e.g., *string* on line 8). Ensure the type formatting is consistent across all field definitions in this documentation.

Apply this diff to align the formatting:

-| `SchemaVersion`                                                                                                                                                                        | **string*                                                                                                                                                                              | :heavy_minus_sign:                                                                                                                                                                     | Schema version to use for validation                                                                                                                                                   | v1.0.0                                                                                                                                                                                 |
+| `SchemaVersion`                                                                                                                                                                        | *string*                                                                                                                                                                               | :heavy_minus_sign:                                                                                                                                                                     | Schema version to use for validation                                                                                                                                                   | v1.0.0                                                                                                                                                                                 |
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| `SchemaVersion` | **string* | :heavy_minus_sign: | Schema version to use for validation | v1.0.0 |
| `SchemaVersion` | *string* | :heavy_minus_sign: | Schema version to use for validation | v1.0.0 |
🤖 Prompt for AI Agents
In pkg/client/docs/models/operations/v2createtransactionrequest.md around line
12, the type markup for SchemaVersion is written as `**string*` which is
inconsistent with the other fields (e.g., `*string*` on line 8); update the
table entry to use the same `*string*` formatting for the type so all
required-field type notations are consistent.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (6)
internal/api/v2/controllers_schema_insert_test.go (1)

58-64: “invalid JSON” case currently sends valid JSON

requestBody is a plain string, but it’s marshaled with json.Marshal, so the body is valid JSON (a JSON string), not syntactically invalid JSON. If you want to exercise JSON decoding failures, consider either:

  • Writing the raw body without json.Marshal, or
  • Renaming the case to something like "invalid schema payload" to reflect a type/validation error instead of malformed JSON.
internal/errors.go (1)

91-121: New schema/account errors look consistent; minor message/Unwrap tweaks

The new ErrInvalidSchema / ErrInvalidAccount types fit the existing error pattern (value types with Is methods) and give good context (path + segment).

Two small polish points:

  • In ErrInvalidAccount.Error, the middle branch message says “chart of account” while the others say “chart of accounts” – consider making this consistent.
  • If you ever need to surface the underlying validation error from ErrInvalidSchema, adding an Unwrap() error { return e.err } would make error-chaining more ergonomic; otherwise current design is fine.
internal/controller/ledger/errors.go (1)

314-318: Use named field in constructor.

For clarity and consistency with other constructors in this file, use named field initialization.

 func newErrSchemaAlreadyExists(version string) ErrSchemaAlreadyExists {
 	return ErrSchemaAlreadyExists{
-		version,
+		version: version,
 	}
 }
internal/chart.go (3)

10-10: Document ChartAccountRules as placeholder.

The empty ChartAccountRules struct appears to be a placeholder for future functionality. Consider adding a comment to clarify this is intentional and reserved for future rule definitions.

+// ChartAccountRules defines validation rules for chart accounts (placeholder for future functionality)
 type ChartAccountRules struct{}

96-102: Pattern regex compiled for validation only.

The pattern regex is compiled here for validation (line 98-100) but will be recompiled during account lookups at line 240. If chart unmarshaling performance becomes a concern, consider caching the compiled regex.


264-273: Consider explicit "world" account definition.

The special-case handling of the "world" account (lines 267-270) bypasses chart validation. Consider whether "world" should be explicitly defined in chart schemas or documented as a reserved system account that exists outside the chart.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 4ed3f6c and 5cc5a53.

📒 Files selected for processing (13)
  • internal/README.md (50 hunks)
  • internal/api/common/errors.go (2 hunks)
  • internal/api/v2/controllers_schema_insert_test.go (1 hunks)
  • internal/chart.go (1 hunks)
  • internal/chart_test.go (1 hunks)
  • internal/controller/ledger/controller_default_test.go (7 hunks)
  • internal/controller/ledger/errors.go (1 hunks)
  • internal/controller/ledger/log_process.go (1 hunks)
  • internal/controller/ledger/log_process_test.go (3 hunks)
  • internal/controller/ledger/store.go (2 hunks)
  • internal/controller/ledger/store_generated_test.go (3 hunks)
  • internal/errors.go (2 hunks)
  • internal/storage/ledger/schema.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • internal/api/common/errors.go
  • internal/chart_test.go
  • internal/controller/ledger/controller_default_test.go
  • internal/controller/ledger/log_process_test.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-04-29T11:24:28.923Z
Learnt from: gfyrag
Repo: formancehq/ledger PR: 892
File: internal/controller/ledger/controller_default.go:196-196
Timestamp: 2025-04-29T11:24:28.923Z
Learning: In the ledger Import function, it's critical to maintain proper log ID tracking by updating lastLogID with the current log.ID after each processed log, rather than setting it to nil. This ensures the system can properly validate the ordering of logs and prevent duplicate or out-of-order processing, which is essential for maintaining data integrity in the ledger.

Applied to files:

  • internal/README.md
🧬 Code graph analysis (7)
internal/storage/ledger/schema.go (3)
internal/controller/ledger/store.go (1)
  • Store (29-67)
internal/schema.go (1)
  • Schema (15-21)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (26-29)
internal/controller/ledger/errors.go (1)
internal/api/common/errors.go (2)
  • ErrSchemaNotSpecified (28-28)
  • ErrSchemaAlreadyExists (27-27)
internal/controller/ledger/store_generated_test.go (2)
internal/schema.go (1)
  • Schema (15-21)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (26-29)
internal/controller/ledger/store.go (2)
internal/schema.go (1)
  • Schema (15-21)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (26-29)
internal/controller/ledger/log_process.go (4)
internal/schema.go (1)
  • Schema (15-21)
internal/controller/ledger/errors.go (3)
  • ErrNotFound (14-14)
  • ErrSchemaNotFound (249-252)
  • ErrSchemaNotSpecified (288-290)
internal/api/common/errors.go (1)
  • ErrSchemaNotSpecified (28-28)
internal/log.go (2)
  • NewLog (194-199)
  • ComputeIdempotencyHash (459-468)
internal/api/v2/controllers_schema_insert_test.go (3)
internal/controller/ledger/errors.go (1)
  • ErrSchemaValidationError (267-270)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (135-138)
internal/api/v2/routes.go (1)
  • NewRouter (28-135)
internal/chart.go (1)
internal/errors.go (1)
  • ErrInvalidAccount (103-107)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: GoReleaser
  • GitHub Check: Tests
  • GitHub Check: Dirty
🔇 Additional comments (13)
internal/api/v2/controllers_schema_insert_test.go (1)

21-85: Solid table-driven coverage and gomock expectations

Nice use of table-driven tests with tc := tc and parallel subtests, and precise UpdateSchema expectations tied to expectBackendCall. The cases cover the main behaviors (success, empty data, malformed payload, backend error, and schema validation error) and validate both HTTP status and error codes.

internal/controller/ledger/log_process.go (2)

112-118: SchemaVersion propagation and validation are wired correctly

Setting log.SchemaVersion = parameters.SchemaVersion and validating with log.ValidateWithSchema(*schema) when a schema was resolved cleanly ties the stored log to the chosen schema version and surfaces validation failures via newErrSchemaValidationError as expected.


72-103: No safety issue with zero-value payload.NeedsSchema()

Verification confirms all LogPayload implementations—CreatedTransaction, SavedMetadata, DeletedMetadata, RevertedTransaction, and UpdatedSchema—use value receivers and return a bool constant without accessing receiver state. Calling NeedsSchema() on a zero value is safe and does not panic. The review concern does not apply to this codebase.

Likely an incorrect or invalid review comment.

internal/controller/ledger/store.go (1)

10-21: Schema methods on Store interface look consistent with storage layer

The added InsertSchema, FindSchema, FindSchemas, and FindLatestSchemaVersion methods fit cleanly into the existing Store interface and match the storage implementation types (ledger.Schema, bunpaginate.Cursor, common.PaginatedQuery[any]). No issues spotted.

Also applies to: 49-52

internal/storage/ledger/schema.go (1)

15-61: Schema persistence helpers are correctly scoped and error-normalized

InsertSchema, FindSchema, FindLatestSchemaVersion, and FindSchemas all:

  • Scope queries to the current ledger via the ledger column and GetPrefixedRelationName("schemas").
  • Normalize DB errors with postgres.ResolveError where appropriate.
  • Distinguish “no schema yet” (FindLatestSchemaVersion returning (nil, nil)) from real failures.

This matches how the controller layer expects to handle missing vs existing schemas; implementation looks sound.

internal/controller/ledger/errors.go (1)

267-286: LGTM!

The error type and constructor are well-implemented with clear error messages.

internal/controller/ledger/store_generated_test.go (1)

1-421: LGTM - Generated mock code.

This is auto-generated mock code that properly adds schema-related mock methods (FindLatestSchemaVersion, FindSchema, FindSchemas, InsertSchema) with correct gomock patterns.

internal/chart.go (5)

44-69: LGTM!

The root-level validation correctly prevents variable segments and account properties at the root level, with clear error messages.


113-133: LGTM!

The validation logic correctly enforces that:

  • Variable segments (with patterns) must have $ prefix
  • Fixed segments (without patterns) cannot have $ prefix
  • Only one variable segment is allowed per level

135-164: LGTM!

Property handling correctly validates .self as an empty object and properly unmarshals .metadata and .rules. The implicit account creation for leaf nodes (line 156) is a sensible default.


166-222: LGTM!

The JSON marshaling methods correctly reconstruct the chart structure, including proper handling of the .self marker for accounts with child segments and pattern inclusion for variable segments.


275-285: LGTM!

The posting validation correctly ensures both source and destination accounts exist in the chart of accounts.

internal/README.md (1)

1-1962: LGTM - Generated documentation.

Auto-generated documentation comprehensively covers all new schema and chart-of-accounts types, methods, and constants introduced in this PR.

Comment on lines +249 to +265
type ErrSchemaNotFound struct {
requestedVersion string
latestVersion *string
}

func (e ErrSchemaNotFound) Error() string {
if e.latestVersion != nil {
return fmt.Sprintf("schema version `%s` not found, latest available version is `%s`", e.requestedVersion, *e.latestVersion)
} else {
return fmt.Sprintf("schema version `%s` not found, this ledger doesn't have a schema", e.requestedVersion)
}
}

func (e ErrSchemaNotFound) Is(err error) bool {
_, ok := err.(ErrSchemaNotFound)
return ok
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Add constructor for ErrSchemaNotFound.

The error type is missing a constructor function. For consistency with other error types in this file (e.g., NewErrIdempotencyKeyConflict at line 123, newErrAlreadyReverted at line 104), add a constructor to instantiate this error properly.

+func newErrSchemaNotFound(requestedVersion string) ErrSchemaNotFound {
+	return ErrSchemaNotFound{
+		requestedVersion: requestedVersion,
+	}
+}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
internal/controller/ledger/errors.go around lines 249-265: add a constructor
function (e.g., NewErrSchemaNotFound(requestedVersion string, latestVersion
*string) error) that returns an ErrSchemaNotFound instance populated with the
provided requestedVersion and latestVersion; implement it to return the concrete
ErrSchemaNotFound value (or error interface) so callers can create this error
consistently like other errors in this file.

Comment on lines +288 to +299
type ErrSchemaNotSpecified struct {
latestVersion string
}

func (e ErrSchemaNotSpecified) Error() string {
return fmt.Sprintf("a schema version must be specified for this ledger, latest available version is %v", e.latestVersion)
}

func (e ErrSchemaNotSpecified) Is(err error) bool {
_, ok := err.(ErrSchemaNotSpecified)
return ok
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Add constructor and fix format string.

Two issues:

  1. Missing constructor function - add one for consistency with other error types in this file
  2. Line 293: Use %s instead of %v for the string field latestVersion
 func (e ErrSchemaNotSpecified) Error() string {
-	return fmt.Sprintf("a schema version must be specified for this ledger, latest available version is %v", e.latestVersion)
+	return fmt.Sprintf("a schema version must be specified for this ledger, latest available version is %s", e.latestVersion)
 }
+
+func newErrSchemaNotSpecified(latestVersion string) ErrSchemaNotSpecified {
+	return ErrSchemaNotSpecified{
+		latestVersion: latestVersion,
+	}
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
type ErrSchemaNotSpecified struct {
latestVersion string
}
func (e ErrSchemaNotSpecified) Error() string {
return fmt.Sprintf("a schema version must be specified for this ledger, latest available version is %v", e.latestVersion)
}
func (e ErrSchemaNotSpecified) Is(err error) bool {
_, ok := err.(ErrSchemaNotSpecified)
return ok
}
type ErrSchemaNotSpecified struct {
latestVersion string
}
func (e ErrSchemaNotSpecified) Error() string {
return fmt.Sprintf("a schema version must be specified for this ledger, latest available version is %s", e.latestVersion)
}
func (e ErrSchemaNotSpecified) Is(err error) bool {
_, ok := err.(ErrSchemaNotSpecified)
return ok
}
func newErrSchemaNotSpecified(latestVersion string) ErrSchemaNotSpecified {
return ErrSchemaNotSpecified{
latestVersion: latestVersion,
}
}
🤖 Prompt for AI Agents
internal/controller/ledger/errors.go around lines 288 to 299: the
ErrSchemaNotSpecified type lacks the standard constructor and its Error() method
uses the wrong format verb; add a constructor function (e.g.,
NewErrSchemaNotSpecified(latestVersion string) ErrSchemaNotSpecified or error to
match other file patterns) that initializes and returns the error, and update
Error() to use %s for the latestVersion string in fmt.Sprintf; keep the existing
Is method behavior to continue type comparisons.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
internal/chart_test.go (1)

378-402: Add descriptive names to test cases.

Test cases at lines 387 and 395 have empty name fields, making debugging more difficult if tests fail.

Apply this diff:

 		{
-			name: "",
+			name: "invalid source account",
 			posting: Posting{
 				Source:      "bank:invalid",
 				Destination: "users:001:main",
 			},
 			expectError: true,
 		},
 		{
-			name: "",
+			name: "invalid destination account",
 			posting: Posting{
 				Source:      "bank:012",
 				Destination: "users:invalid:main",
 			},
 			expectError: true,
 		},
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5cc5a53 and f6c0feb.

📒 Files selected for processing (3)
  • internal/README.md (50 hunks)
  • internal/chart.go (1 hunks)
  • internal/chart_test.go (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-04-29T11:24:28.923Z
Learnt from: gfyrag
Repo: formancehq/ledger PR: 892
File: internal/controller/ledger/controller_default.go:196-196
Timestamp: 2025-04-29T11:24:28.923Z
Learning: In the ledger Import function, it's critical to maintain proper log ID tracking by updating lastLogID with the current log.ID after each processed log, rather than setting it to nil. This ensures the system can properly validate the ordering of logs and prevent duplicate or out-of-order processing, which is essential for maintaining data integrity in the ledger.

Applied to files:

  • internal/README.md
🧬 Code graph analysis (2)
internal/chart_test.go (2)
internal/chart.go (5)
  • ChartOfAccounts (36-36)
  • ChartVariableSegment (23-28)
  • ChartSegment (17-21)
  • ChartAccount (12-15)
  • ChartAccountRules (10-10)
internal/errors.go (1)
  • ErrInvalidAccount (103-107)
internal/chart.go (2)
internal/storage/ledger/logs.go (1)
  • RawMessage (38-38)
internal/errors.go (1)
  • ErrInvalidAccount (103-107)
🔇 Additional comments (10)
internal/chart_test.go (3)

10-244: LGTM! Comprehensive validation testing.

The table-driven tests thoroughly cover both valid and invalid chart configurations, including edge cases like invalid segments, pattern conflicts, and structural invariants. The JSON round-trip verification ensures marshaling correctness.


246-288: LGTM! Well-designed test fixture.

The helper provides a comprehensive chart structure covering fixed segments, variable segments with patterns, and nested accounts with metadata—ideal for testing diverse validation scenarios.


290-365: LGTM! Thorough account resolution testing.

The tests validate account schema lookup across various scenarios, including fixed segments, variable segments with pattern matching, nested paths, and multiple error conditions with precise error message verification.

internal/chart.go (6)

10-42: LGTM! Clean type definitions and validation.

The hierarchical chart structure is well-designed with clear separation between fixed and variable segments. The global ChartSegmentRegexp is safe as it's read-only after initialization. ChartAccountRules is currently an empty struct, which is fine as a placeholder for future extensions.


44-69: LGTM! Robust root-level validation.

The method properly enforces root-level constraints (no variable segments or account properties) and provides clear error messages for invalid configurations.


70-164: LGTM! Comprehensive segment unmarshaling.

The method handles the complex logic of distinguishing fixed from variable segments, validates patterns, enforces structural constraints (one variable segment per level, patterns only on $ prefixes), and automatically treats leaf segments as accounts. The nested validation block (lines 89-107) properly validates regex patterns before use.


166-222: LGTM! Symmetric JSON marshaling.

The marshaling logic properly mirrors the unmarshaling implementation, ensuring JSON round-trip consistency. The marshalJsonObject helper is well-structured and correctly adds the .self marker only when a segment is both an account and has subsegments (line 200).


224-270: LGTM! Sound recursive account resolution.

The findAccountSchema function correctly prioritizes fixed segments over variable segments, validates patterns with regex matching, and provides detailed error context through ErrInvalidAccount. The public FindAccountSchema wrapper properly splits the account address and delegates to the recursive helper.


272-282: LGTM! Straightforward posting validation.

The method correctly validates that both source and destination accounts exist in the chart by delegating to FindAccountSchema. The error handling properly propagates account lookup failures.

internal/README.md (1)

1-1962: LGTM! Generated documentation is comprehensive.

This auto-generated documentation properly reflects the new schema and chart of accounts functionality. The index is updated, and all new types, methods, constants, and variables are documented with appropriate links and signatures.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (8)
internal/errors.go (1)

91-101: Optional: expose underlying schema error and be mindful of string-coupled messages.

The new error types look consistent with existing patterns and work well with errors.Is. Two small optional improvements:

  • For ErrInvalidSchema, consider adding an Unwrap() error method so callers can inspect the root cause without parsing the formatted message.
  • ErrInvalidAccount.Error strings are now asserted verbatim in tests (e.g., chart of account vs chart of accounts). That’s fine, but it does mean any future copy tweaks must be coordinated with tests and external consumers that may rely on exact text.

Also applies to: 103-126

test/e2e/api_schema_test.go (1)

1-462: Strong end‑to‑end coverage for schema lifecycle and validation.

This suite nicely exercises schema insert/read/list, transaction validation across versions, default metadata behavior, error codes, and log schemaVersion propagation. If you ever want to reduce repetition, you could factor small helpers for the repeated CreateTransaction and InsertSchema calls, but it’s not necessary for clarity.

internal/chart_test.go (2)

13-253: Comprehensive chart and account validation tests, with minor ergonomics nits.

The coverage here is excellent: you’re asserting both structural parsing (valid/invalid charts), exact error strings from ErrInvalidAccount, and that posting validation routes through that error type. Two minor nits you might consider later:

  • Negative TestPostingValidation cases have empty name fields, which makes failures slightly harder to read; giving them descriptive names would help.
  • Because many assertions depend on exact error messages, any future wording change in ErrInvalidAccount.Error() will require synchronized test updates.

Overall, behavior looks solid.

Also applies to: 299-379, 381-425


427-465: Default metadata tests correctly capture in‑place mutation semantics.

InsertDefaultAccountMetadata mutates the passed metadata.Metadata map, and the tests assert on the mutated previousMetadata, which matches the function’s contract and the controller’s usage. If you ever change the API to return a new map instead, these tests will be a good guide for updating callers.

internal/controller/ledger/log_process.go (2)

65-104: Schema resolution and validation flow looks consistent; just ensure NeedsSchema is instance‑agnostic.

The new runLog logic correctly handles:

  • Explicit SchemaVersion: load via FindSchema, translate not‑found into ErrSchemaNotFound with latest version.
  • Implicit schema: for payloads where OUTPUT.NeedsSchema() is true, require a schema only once the ledger actually has one, otherwise allow legacy behavior.
  • Post‑operation validation via log.ValidateWithSchema(*schema) and newErrSchemaValidationError.

One assumption worth double‑checking is that NeedsSchema() on all ledger.LogPayload implementations is a pure function of the payload type (and implemented on a value receiver), since it’s invoked on a zero‑value var payload OUTPUT. If any implementation ever depends on initialized fields, this could misclassify whether a schema is required.

Also applies to: 114-118


105-113: Consider whether SchemaVersion should participate in idempotency hashing.

IdempotencyHash is still computed solely from parameters.Input, while schema selection and validation now depend on parameters.SchemaVersion. That means two calls with the same IK and input but different SchemaVersion will be treated as the same idempotent request and will not surface a conflict, even though they may conceptually target different validation rules.

If you want schema version to be part of the idempotent “contract”, consider folding SchemaVersion into the idempotency hash (or explicitly documenting that idempotency is defined only over the transaction payload, not the schema used to validate it).

Also applies to: 176-193

internal/controller/ledger/controller_default.go (1)

381-452: Schema‑aware transaction creation and metadata flows are consistent with chart behavior.

Threading *ledger.Schema into createTransaction and using:

if schema != nil {
    schema.Chart.InsertDefaultAccountMetadata(account, accountMetadata[account])
}

after merging explicit AccountMetadata gives the desired semantics:

  • Default metadata is applied per account only when a schema is present and that account already has some metadata entry (matching InsertDefaultAccountMetadata tests).
  • Revert and metadata‑only operations accept the schema parameter but ignore it, which keeps their behavior unchanged while satisfying the new logProcessor signature.

This matches the expectations encoded in internal/chart_test.go and the E2E schema tests.

Also applies to: 528-600

docs/api/README.md (1)

685-735: Schema‑related docs align with implementation; markdownlint warnings are mostly generator noise.

The additions for:

  • schemaVersion query params (bulk, create/revert transaction, and metadata endpoints),
  • the new schemaVersion field and UPDATED_SCHEMA log type in V2Log/V2LogsCursorResponse,
  • and the chart/schema models (V2Chart*, V2Schema*, V2SchemasCursor*),

all match the controller/store behavior and the Go client models. That’s good.

The markdownlint warnings (MD024 duplicate headings like “### Properties”, MD028 blank lines in blockquotes) largely stem from Widdershins‑generated structure rather than these specific changes. If you care about a clean lint run, it’s probably better to adjust the linter config or generator template globally rather than hand‑tweaking this section.

Also applies to: 1139-1140, 1566-1567, 1842-1843, 1907-1908, 2219-2221, 3495-3508, 4022-4044, 4497-4556, 5612-5877

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f6c0feb and e253445.

⛔ Files ignored due to path filters (6)
  • docs/events/UpdatedSchema.json is excluded by !**/*.json
  • openapi.yaml is excluded by !**/*.yaml
  • openapi/v2.yaml is excluded by !**/*.yaml
  • pkg/client/.speakeasy/gen.lock is excluded by !**/*.lock, !**/*.lock
  • pkg/client/.speakeasy/logs/naming.log is excluded by !**/*.log, !**/*.log
  • pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log is excluded by !**/*.log, !**/*.log
📒 Files selected for processing (15)
  • docs/api/README.md (22 hunks)
  • internal/README.md (50 hunks)
  • internal/chart.go (1 hunks)
  • internal/chart_test.go (1 hunks)
  • internal/controller/ledger/controller_default.go (10 hunks)
  • internal/controller/ledger/log_process.go (3 hunks)
  • internal/controller/ledger/log_process_test.go (5 hunks)
  • internal/errors.go (2 hunks)
  • pkg/client/docs/models/components/v2chartaccountmetadata.md (1 hunks)
  • pkg/client/docs/models/components/v2chartaccountrules.md (1 hunks)
  • pkg/client/docs/models/components/v2chartsegment.md (1 hunks)
  • pkg/client/models/components/v2chartaccountmetadata.go (1 hunks)
  • pkg/client/models/components/v2chartaccountrules.go (1 hunks)
  • pkg/client/models/components/v2chartsegment.go (1 hunks)
  • test/e2e/api_schema_test.go (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • pkg/client/models/components/v2chartaccountrules.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • pkg/client/docs/models/components/v2chartsegment.md
  • pkg/client/models/components/v2chartsegment.go
  • internal/chart.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-04-29T11:24:28.923Z
Learnt from: gfyrag
Repo: formancehq/ledger PR: 892
File: internal/controller/ledger/controller_default.go:196-196
Timestamp: 2025-04-29T11:24:28.923Z
Learning: In the ledger Import function, it's critical to maintain proper log ID tracking by updating lastLogID with the current log.ID after each processed log, rather than setting it to nil. This ensures the system can properly validate the ordering of logs and prevent duplicate or out-of-order processing, which is essential for maintaining data integrity in the ledger.

Applied to files:

  • internal/controller/ledger/controller_default.go
  • internal/README.md
🧬 Code graph analysis (4)
internal/controller/ledger/log_process.go (6)
internal/controller/ledger/store.go (1)
  • Store (29-67)
internal/storage/ledger/store.go (1)
  • Store (25-50)
internal/schema.go (1)
  • Schema (15-21)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/log.go (1)
  • Log (89-102)
internal/controller/ledger/errors.go (3)
  • ErrNotFound (14-14)
  • ErrSchemaNotFound (249-252)
  • ErrSchemaNotSpecified (288-290)
test/e2e/api_schema_test.go (8)
pkg/client/models/components/v2schemascursorresponse.go (1)
  • V2SchemasCursorResponse (5-7)
pkg/client/models/components/v2schemadata.go (1)
  • V2SchemaData (6-9)
pkg/client/models/components/v2chartaccountmetadata.go (1)
  • V2ChartAccountMetadata (5-7)
pkg/client/models/components/v2errorsenum.go (4)
  • V2ErrorsEnumSchemaAlreadyExists (30-30)
  • V2ErrorsEnumValidation (15-15)
  • V2ErrorsEnumSchemaNotSpecified (31-31)
  • V2ErrorsEnumNotFound (19-19)
pkg/client/models/operations/v2getschema.go (1)
  • V2GetSchemaRequest (9-14)
pkg/client/models/components/v2schemaresponse.go (1)
  • V2SchemaResponse (5-8)
pkg/client/models/components/v2posting.go (1)
  • V2Posting (10-15)
pkg/client/models/components/v2log.go (1)
  • V2Log (48-56)
internal/controller/ledger/controller_default.go (4)
internal/controller/ledger/controller.go (4)
  • UpdateSchema (135-138)
  • RevertTransaction (108-113)
  • DeleteTransactionMetadata (125-128)
  • DeleteAccountMetadata (130-133)
internal/log.go (2)
  • UpdatedSchema (418-420)
  • Log (89-102)
internal/storage/ledger/store.go (1)
  • Store (25-50)
internal/schema.go (2)
  • Schema (15-21)
  • NewSchema (23-33)
internal/controller/ledger/log_process_test.go (4)
internal/controller/ledger/store.go (1)
  • Store (29-67)
internal/schema.go (1)
  • Schema (15-21)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/controller/ledger/controller.go (1)
  • RunScript (91-91)
🪛 markdownlint-cli2 (0.18.1)
docs/api/README.md

357-357: Blank line inside blockquote

(MD028, no-blanks-blockquote)


403-403: Blank line inside blockquote

(MD028, no-blanks-blockquote)


461-461: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4

(MD001, heading-increment)


470-470: Blank line inside blockquote

(MD028, no-blanks-blockquote)


5624-5624: Multiple headings with the same content

(MD024, no-duplicate-heading)


5626-5626: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


5642-5642: Multiple headings with the same content

(MD024, no-duplicate-heading)


5668-5668: Multiple headings with the same content

(MD024, no-duplicate-heading)


5699-5699: Multiple headings with the same content

(MD024, no-duplicate-heading)


5727-5727: Multiple headings with the same content

(MD024, no-duplicate-heading)


5759-5759: Multiple headings with the same content

(MD024, no-duplicate-heading)


5793-5793: Multiple headings with the same content

(MD024, no-duplicate-heading)


5832-5832: Multiple headings with the same content

(MD024, no-duplicate-heading)


5869-5869: Multiple headings with the same content

(MD024, no-duplicate-heading)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: GoReleaser
  • GitHub Check: Tests
  • GitHub Check: Dirty
🔇 Additional comments (5)
pkg/client/docs/models/components/v2chartaccountrules.md (1)

1-7: Documentation appears incomplete or unclear.

The table structure has headers but contains no field rows. This suggests either the struct is empty (which should be documented with an explanatory note) or the documentation is incomplete. Please clarify the intent and populate the table accordingly or add a note explaining why no fields are present.

To verify the structure of V2ChartAccountRules, please check the corresponding Go struct definition in pkg/client/models/components/v2chartaccountrules.go and confirm whether it actually contains any fields or is intentionally empty. If the struct has fields, please populate the documentation table. If it is intentionally empty, consider adding a brief explanatory note in the documentation.

pkg/client/models/components/v2chartaccountmetadata.go (1)

1-14: LGTM!

The generated code follows best practices:

  • Proper nil-receiver safety in the getter method
  • Correct use of pointer type with omitempty JSON tag for optional fields
  • Clean, idiomatic Go structure
internal/controller/ledger/log_process_test.go (1)

35-55: Tests correctly exercise schema-aware forgeLog behavior.

The added FindLatestSchemaVersion expectations and the updated callback signature (schema *ledger.Schema) align with the new runLog contract and retry logic. Both conflict and deadlock paths now assert the expected schema lookups without over-constraining behavior.

Also applies to: 70-107

internal/controller/ledger/controller_default.go (1)

54-84: Schema controller wiring and import behavior look correct.

The new UpdateSchema/GetSchema/ListSchemas methods, the updateSchemaLp wiring, and the ledger.UpdatedSchema import case all line up with the store interface and error handling:

  • ledger.NewSchema + InsertSchema with ErrSchemaAlreadyExists on constraint failures ensures invalid/duplicate schemas are rejected cleanly.
  • Using a pointer in InsertSchema guarantees CreatedAt is populated before the UpdatedSchema payload is returned or logged.
  • Importing ledger.UpdatedSchema by calling InsertSchema before re‑inserting the log matches how transaction imports replay side effects.

No issues from a correctness standpoint.

Also applies to: 157-164, 271-281

internal/README.md (1)

1-1: Original review comment is incorrect.

The file internal/README.md is properly managed. It's auto-generated via Go's standard //go:generate directive (declared in internal/doc.go), which is designed for files that are committed to version control. Developers regenerate it by running go generate ./... locally when the underlying code changes.

In Go projects, go:generate outputs are meant to be committed—this is idiomatic Go practice and allows:

  • Users without generator tools installed to access documentation
  • Version control history of documentation changes
  • Consistency across environments

The file is correctly excluded from .gitignore, confirming it should be committed.

Likely an incorrect or invalid review comment.


| Field | Type | Required | Description |
| ------------------ | ------------------ | ------------------ | ------------------ |
| `Default` | **string* | :heavy_minus_sign: | N/A | No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix markdown formatting for the type column.

The type column has mismatched asterisks: **string* should be *string* (to indicate a pointer type in italics) or **string** (for bold).

Apply this diff to fix the formatting:

-| `Default`          | **string*          | :heavy_minus_sign: | N/A                |
+| `Default`          | *string*           | :heavy_minus_sign: | N/A                |
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| `Default` | **string* | :heavy_minus_sign: | N/A |
| `Default` | *string* | :heavy_minus_sign: | N/A |
🤖 Prompt for AI Agents
In pkg/client/docs/models/components/v2chartaccountmetadata.md around line 8,
the type column entry currently reads `**string*` which has mismatched
asterisks; replace it with `*string*` (or `**string**` if bold is intended) to
fix the Markdown formatting so the type renders correctly—update the table cell
to use balanced asterisks and save the file.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/controller/ledger/controller_default.go (1)

454-518: RevertTransaction bypasses schema validation by passing nil schema to CommitTransaction

In revertTransaction, the new signature accepts _schema *ledger.Schema, but the final CommitTransaction call ignores it:

// ...
err = store.CommitTransaction(ctx, nil, &reversedTx, nil)

Whereas createTransaction passes the provided schema through to CommitTransaction. If the goal is to enforce chart/schema validation for all postings that hit the ledger, you likely want:

err = store.CommitTransaction(ctx, _schema, &reversedTx, nil)

so reversals are validated under the same schema as normal creations. If you intentionally want reversals to always succeed even when the current schema would reject them (e.g., schema tightened after the original tx), it would be helpful to document that rationale near this call.

Also applies to: 509-512

♻️ Duplicate comments (15)
pkg/client/docs/models/operations/v2listtransactionsrequest.md (1)

13-13: Markdown formatting issue persists: spaces inside emphasis markers.

The deprecation notice still has spaces inside the bold markers (** DEPRECATED **), which violates markdown formatting rules (MD037). This was previously flagged and marked as addressed, but appears to have regressed (possibly due to regeneration).

-| : warning: ** DEPRECATED **: This will be removed in a future release...
+| :warning: **DEPRECATED**: This will be removed in a future release...

If this file is auto-generated, the fix should be applied to the OpenAPI spec or generator template.

pkg/client/docs/models/operations/v2listschemasrequest.md (1)

9-10: Fix Markdown formatting for optional pointer types.

The Type column has malformed Markdown emphasis with mismatched asterisks that will render incorrectly.

Apply this diff to fix the formatting:

-| `Cursor`                                              | **string*                                             | :heavy_minus_sign:                                    | The pagination cursor value                           |                                                       |
-| `PageSize`                                            | **int64*                                              | :heavy_minus_sign:                                    | The maximum number of results to return per page      |                                                       |
+| `Cursor`                                              | *string*                                              | :heavy_minus_sign:                                    | The pagination cursor value                           |                                                       |
+| `PageSize`                                            | *int64*                                               | :heavy_minus_sign:                                    | The maximum number of results to return per page      |                                                       |
pkg/client/docs/models/components/v2chartsegment.md (1)

11-13: Fix type formatting for DotPattern and DotMetadata.

The formatting issues with DotPattern (showing as **string*) and DotMetadata have already been flagged in previous review comments but remain unresolved. Please address the Markdown formatting corrections as suggested by previous reviewers.

internal/api/common/errors.go (1)

51-67: Wire ErrSchemaAlreadyExists into HandleCommonWriteErrors.

The constant ErrSchemaAlreadyExists is defined but never mapped in HandleCommonWriteErrors. When ledgercontroller.ErrSchemaAlreadyExists occurs, it will fall through to the default case and likely surface as a 500 Internal Server Error instead of the expected 409 Conflict.

Based on past review comments, add an explicit branch:

 func HandleCommonWriteErrors(w http.ResponseWriter, r *http.Request, err error) {
 	switch {
 	case errors.Is(err, ledgercontroller.ErrIdempotencyKeyConflict{}):
 		api.WriteErrorResponse(w, http.StatusConflict, ErrConflict, err)
 	case errors.Is(err, ledgercontroller.ErrInvalidIdempotencyInput{}):
 		api.BadRequest(w, ErrValidation, err)
 	case errors.Is(err, ledgercontroller.ErrNotFound):
 		api.NotFound(w, err)
+	case errors.Is(err, ledgercontroller.ErrSchemaAlreadyExists{}):
+		api.WriteErrorResponse(w, http.StatusConflict, ErrSchemaAlreadyExists, err)
 	case errors.Is(err, ledgercontroller.ErrSchemaValidationError{}):
 		api.BadRequest(w, ErrValidation, errors.Unwrap(err))
 	case errors.Is(err, ledgercontroller.ErrSchemaNotSpecified{}):
 		api.BadRequest(w, ErrSchemaNotSpecified, errors.Unwrap(err))
 	case errors.Is(err, ledgercontroller.ErrSchemaNotFound{}):
 		api.NotFound(w, errors.Unwrap(err))
 	default:
 		HandleCommonErrors(w, r, err)
 	}
 }
internal/api/v2/controllers_schema_insert_test.go (1)

57-64: The "invalid JSON" test case doesn't actually test invalid JSON handling.

The test marshals the string "invalid json string" into valid JSON ("\"invalid json string\"") at line 101, so the handler never receives malformed JSON. To properly test invalid JSON parsing, use json.RawMessage for the request body in this test case or create the request with raw bytes directly.

Consider this approach:

 		{
 			name:              "invalid JSON",
 			version:           "v1.0.0",
-			requestBody:       "invalid json string",
+			requestBody:       json.RawMessage(`{invalid`),
 			expectStatusCode:  http.StatusBadRequest,
 			expectedErrorCode: "VALIDATION",
 			expectBackendCall: false,
 		},

And adjust line 101 to handle json.RawMessage:

-			body, _ := json.Marshal(tc.requestBody)
+			var body []byte
+			if raw, ok := tc.requestBody.(json.RawMessage); ok {
+				body = raw
+			} else {
+				body, _ = json.Marshal(tc.requestBody)
+			}
pkg/client/docs/sdks/v2/README.md (2)

240-310: Documentation for InsertSchema is comprehensive and follows SDK patterns.

The new InsertSchema operation documentation includes proper example usage, parameter table, response type, and error types. The example correctly demonstrates creating a schema with chart segments and patterns.

Note: Static analysis indicates hard tabs in the code example (lines 250-255), which was previously flagged. This appears to be a generated file formatting issue.


311-429: GetSchema and ListSchemas documentation sections are well-structured.

Both sections follow the established documentation pattern with example usage, parameter tables, and error types. The examples correctly demonstrate the expected API usage.

The hard tabs issue noted by static analysis was already flagged in previous reviews.

internal/storage/ledger/accounts.go (1)

103-210: Reintroduce ON CONFLICT semantics in UpsertAccounts to avoid concurrent insert failures.

The new CTE‑based upsert splits updates and inserts:

  • existing_accounts selects current rows.
  • inserted_rows does a plain INSERT ... FROM data_batch d WHERE d.address NOT IN (SELECT address FROM existing_accounts).

Under concurrent load, two transactions upserting the same (ledger, address) can both see the row as “not existing” and attempt an insert, hitting the unique (ledger, address) constraint instead of behaving idempotently as before.

To retain safe concurrent upserts, inserted_rows (or the whole statement) needs an ON CONFLICT (ledger, address) clause (e.g., DO NOTHING or DO UPDATE with appropriate merge logic) rather than relying on a precomputed NOT IN filter.

I’d recommend redesigning inserted_rows along these lines:

  • Drop the existing_accounts / NOT IN check, and
  • Use INSERT ... ON CONFLICT (ledger, address) DO NOTHING RETURNING ... (or DO UPDATE mirroring the update logic) so concurrent upserts can’t break on uniqueness.
internal/controller/ledger/controller_default_test.go (1)

72-79: Fix InsertLog mocks: DoAndReturn return type must match Store.InsertLog (error, not any).

Both InsertLog expectations here return any from DoAndReturn, while the other InsertLog mocks in this file return error, indicating the real signature is likely:

InsertLog(ctx context.Context, log *ledger.Log) error

Leaving these as-is will either fail to compile or panic at runtime when gomock tries to invoke the stub.

Recommended change for both expectations:

-       DoAndReturn(func(_ context.Context, log *ledger.Log) any {
-               log.ID = pointer.For(uint64(0))
-               return log
-       })
+       DoAndReturn(func(_ context.Context, log *ledger.Log) error {
+               log.ID = pointer.For(uint64(0))
+               return nil
+       })

Apply the same pattern at both call sites so all InsertLog mocks consistently return error.

Also applies to: 131-138

internal/storage/ledger/resource_schemas.go (1)

25-35: Prefer store.newScopedSelect() over db.NewSelect() + manual ledger filter.

Other ledger resource handlers use store.newScopedSelect() to inject the optimized ledger predicate; rebuilding the query from db.NewSelect() and a raw WHERE ledger = ? here bypasses that optimization and may regress Postgres query plans.

Consider:

-func (h schemasResourceHandler) BuildDataset(opts common.RepositoryHandlerBuildContext[any]) (*bun.SelectQuery, error) {
-    q := h.store.db.NewSelect().
-        ModelTableExpr(h.store.GetPrefixedRelationName("schemas")).
-        Where("ledger = ?", h.store.ledger.Name)
+func (h schemasResourceHandler) BuildDataset(opts common.RepositoryHandlerBuildContext[any]) (*bun.SelectQuery, error) {
+    q := h.store.newScopedSelect().
+        ModelTableExpr(h.store.GetPrefixedRelationName("schemas"))

keeping the PIT condition below as-is.

internal/controller/ledger/controller_with_traces.go (1)

474-495: Align span name with method/metric (UpdateSchema vs UpdatedSchema).

The span name "UpdatedSchema" is inconsistent with the method name UpdateSchema and the metric "controller.update_schema", which makes traces harder to correlate with code and metrics.

-	_, err = tracing.TraceWithMetric(
-		ctx,
-		"UpdatedSchema",
+	_, err = tracing.TraceWithMetric(
+		ctx,
+		"UpdateSchema",
 		c.tracer,
internal/log.go (1)

433-455: Fix HydrateLog unmarshalling: avoid pointer‑to‑interface (still unsafe)

HydrateLog still passes a pointer‑to‑interface into json.Unmarshal:

var payload any
// payload is set to &CreatedTransaction{}, &SavedMetadata{}, etc.
err := json.Unmarshal(data, &payload)

With &payload (*interface{}), encoding/json replaces the interface’s concrete value with generic types (map[string]any, etc.) instead of populating the concrete struct pointer you set. The subsequent reflect.ValueOf(payload).Elem().Interface().(LogPayload) then operates on the wrong type and can panic or silently misbehave.

Unmarshal into the concrete pointer stored in the interface:

-	err := json.Unmarshal(data, &payload)
+	err := json.Unmarshal(data, payload)

This preserves the concrete *CreatedTransaction, *SavedMetadata, *UpdatedSchema, etc., so the final cast to LogPayload is safe and correct.

Go encoding/json: behavior when calling json.Unmarshal with *interface{} vs interface{} containing a concrete pointer
docs/api/README.md (1)

318-379: Wording for POST /v2/{ledger}/schema/{version} still implies update, but implementation is insert‑only

The section title and anchors (## Insert or update a schema for a ledger, insert-or-update-a-schema-for-a-ledger-…) suggest this endpoint both inserts and updates schemas. In code, updateSchema calls store.InsertSchema and maps unique‑constraint violations to SCHEMA_ALREADY_EXISTS, i.e., it’s an insert‑only operation that fails on duplicate versions.

To avoid misleading clients, consider rephrasing this section to something like:

  • “Insert a schema version for a ledger (fails if the version already exists)”

and adjust the heading/description accordingly, while keeping the existing 204 response and error examples.

internal/README.md (2)

1152-1152: Fix UpdatedSchemaLogType comment to match actual serialized value.

Line 1152 documents UpdatedSchemaLogType with a comment stating "UPDATE_CHART_OF_ACCOUNT", but the actual serialized value is "UPDATED_SCHEMA". This documentation error must be corrected in the source file internal/log.go so that the auto-generated documentation reflects the true constant value.

This issue was previously flagged in review comments and needs to be fixed in the underlying source code:

-    UpdatedSchemaLogType // "UPDATE_CHART_OF_ACCOUNT"
+    UpdatedSchemaLogType // "UPDATED_SCHEMA"

253-257: Resolve regex inconsistency between ChartSegmentRegexp and HTTP API account address validation.

ChartSegmentRegexp at line 256 (^(\$|\.)?[a-zA-Z0-9_-]+$) permits hyphens in segment names, but the documented HTTP API account address regex (^\w+(:\w+)*$) only accepts word characters ([A-Za-z0-9_]). This allows chart segments with hyphens that cannot match valid account addresses. The discrepancy must be resolved consistently.

This issue was previously flagged and requires clarification: choose one consistent rule and update both the ChartSegmentRegexp regex in the source and the HTTP API/address validation logic accordingly (either disallow - in segments or broaden address validation to accept hyphens). Verify the intended behavior.

Also applies to: 339-341, 419-548

🧹 Nitpick comments (8)
internal/errors.go (2)

91-101: Consider adding a constructor function and Unwrap() method for consistency.

Other error types in this file (e.g., newErrInvalidLedgerName, NewErrPipelineAlreadyExists) have constructor functions. For consistency and to enable proper error unwrapping with errors.Is/errors.As on the inner error, consider:

+func NewErrInvalidSchema(err error) ErrInvalidSchema {
+	return ErrInvalidSchema{err: err}
+}
+
+func (e ErrInvalidSchema) Unwrap() error {
+	return e.err
+}

103-126: Consider adding constructor function(s) for ErrInvalidAccount.

For consistency with other error types in this file and to make instantiation clearer, consider adding constructor function(s):

+func NewErrInvalidAccountNotDefined(segment string, hasSubsegments bool) ErrInvalidAccount {
+	return ErrInvalidAccount{segment: segment, hasSubsegments: hasSubsegments}
+}
+
+func NewErrInvalidAccountPatternMismatch(path []string, segment string) ErrInvalidAccount {
+	return ErrInvalidAccount{path: path, segment: segment, patternMismatch: true}
+}
+
+func NewErrInvalidAccountSegmentNotAllowed(path []string, segment string) ErrInvalidAccount {
+	return ErrInvalidAccount{path: path, segment: segment}
+}
internal/storage/ledger/balances_test.go (1)

35-35: Test calls updated correctly for new schema parameter.

The updates to UpsertAccounts and CommitTransaction correctly add nil for the new schema parameter, which is appropriate for tests that don't exercise schema-specific behavior.

Consider adding a dedicated test case that validates schema propagation through CommitTransaction and UpsertAccounts with a non-nil schema to ensure end-to-end schema handling works correctly.

Also applies to: 177-177, 188-188

internal/api/bulking/bulker_test.go (1)

433-433: Run call correctly updated for new id parameter.

The addition of the empty string "" as the id parameter aligns with the updated Run signature. This is appropriate for tests that don't require schema version context.

Consider adding a test case that passes a non-empty id (schema version) to validate the schema-aware bulk processing path.

internal/chart_test.go (1)

399-414: Add descriptive test names for error cases.

The test cases on lines 400 and 408 have empty name fields, which makes test output less informative when failures occur. Consider adding descriptive names like "invalid source pattern" and "invalid destination pattern" to improve debugging clarity.

Apply this diff:

 		{
-			name: "",
+			name: "invalid source pattern",
 			posting: Posting{
 				Source:      "bank:invalid",
 				Destination: "users:001:main",
 			},
 			expectError: true,
 		},
 		{
-			name: "",
+			name: "invalid destination pattern",
 			posting: Posting{
 				Source:      "bank:012",
 				Destination: "users:invalid:main",
 			},
 			expectError: true,
 		},
test/e2e/api_schema_test.go (1)

184-197: Ordering assertion may be fragile depending on timing.

The test assumes schemas are ordered by created_at DESC, but all three schemas are inserted sequentially in the BeforeEach block. If the insertion is fast enough that timestamps are identical (same millisecond), the ordering might become non-deterministic.

Consider adding explicit verification that CreatedAt timestamps are actually different, or adjusting the test to not rely on strict version ordering if timestamps could collide.

internal/chart.go (1)

93-112: Avoid recompiling variable‑segment regex on every account lookup

In findAccountSchema, variable‑segment patterns are recompiled on each lookup via regexp.Match, even though they were already validated in ChartSegment.UnmarshalJSON. If chart validation runs on a hot path (e.g. every posting), consider caching a compiled *regexp.Regexp in ChartVariableSegment during unmarshal and using MatchString here to avoid repeated compilation overhead.

For example, you could extend ChartVariableSegment with an unexported re *regexp.Regexp (ignored by JSON), set it where you currently validate PATTERN_KEY, and then:

matches := variableSegment.re.MatchString(nextSegment)

instead of calling regexp.Match on the pattern string each time.

Also applies to: 244-249

internal/log.go (1)

190-192: Clarify NeedsSchema/ValidateWithSchema semantics for non‑transaction payloads

LogPayload.NeedsSchema() returns true for SavedMetadata, DeletedMetadata, and RevertedTransaction, but their ValidateWithSchema implementations are currently no‑ops (always return nil). This makes the intent of NeedsSchema ambiguous: callers may assume a schema‑driven validation actually happens for these payloads.

If the design is:

  • Only transactions are validated against the chart today, and
  • Metadata/revert logs merely record which schema version was in effect,

consider either:

  • Setting NeedsSchema() to false for payloads that don’t actually use the schema, or
  • Implementing concrete validations for them (e.g., account existence/shape checks for metadata targets, or validation of the generated revert transaction) and documenting the behavior.

This will make the schema requirements and validation behavior easier to reason about for future maintainers.

Also applies to: 218-229, 272-279, 324-331, 375-380, 416-425

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between df9dc5e and 42ba087.

⛔ Files ignored due to path filters (7)
  • docs/events/UpdatedSchema.json is excluded by !**/*.json
  • internal/storage/bucket/migrations/42-add-schema/notes.yaml is excluded by !**/*.yaml
  • openapi.yaml is excluded by !**/*.yaml
  • openapi/v2.yaml is excluded by !**/*.yaml
  • pkg/client/.speakeasy/gen.lock is excluded by !**/*.lock, !**/*.lock
  • pkg/client/.speakeasy/logs/naming.log is excluded by !**/*.log, !**/*.log
  • pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log is excluded by !**/*.log, !**/*.log
📒 Files selected for processing (108)
  • cmd/docs_events.go (1 hunks)
  • docs/api/README.md (13 hunks)
  • internal/README.md (51 hunks)
  • internal/account.go (1 hunks)
  • internal/api/bulking/bulker.go (11 hunks)
  • internal/api/bulking/bulker_test.go (1 hunks)
  • internal/api/bulking/mocks_ledger_controller_test.go (3 hunks)
  • internal/api/common/errors.go (3 hunks)
  • internal/api/common/mocks_ledger_controller_test.go (3 hunks)
  • internal/api/v1/mocks_ledger_controller_test.go (3 hunks)
  • internal/api/v2/controllers_bulk.go (1 hunks)
  • internal/api/v2/controllers_schema_insert.go (1 hunks)
  • internal/api/v2/controllers_schema_insert_test.go (1 hunks)
  • internal/api/v2/controllers_schema_list.go (1 hunks)
  • internal/api/v2/controllers_schema_list_test.go (1 hunks)
  • internal/api/v2/controllers_schema_read.go (1 hunks)
  • internal/api/v2/controllers_schema_read_test.go (1 hunks)
  • internal/api/v2/mocks_ledger_controller_test.go (3 hunks)
  • internal/api/v2/query.go (1 hunks)
  • internal/api/v2/routes.go (1 hunks)
  • internal/api/v2/views.go (1 hunks)
  • internal/bus/listener.go (1 hunks)
  • internal/chart.go (1 hunks)
  • internal/chart_test.go (1 hunks)
  • internal/controller/ledger/controller.go (2 hunks)
  • internal/controller/ledger/controller_default.go (12 hunks)
  • internal/controller/ledger/controller_default_test.go (8 hunks)
  • internal/controller/ledger/controller_generated_test.go (3 hunks)
  • internal/controller/ledger/controller_with_events.go (1 hunks)
  • internal/controller/ledger/controller_with_too_many_client_handling.go (2 hunks)
  • internal/controller/ledger/controller_with_traces.go (3 hunks)
  • internal/controller/ledger/errors.go (1 hunks)
  • internal/controller/ledger/listener.go (1 hunks)
  • internal/controller/ledger/listener_generated_test.go (1 hunks)
  • internal/controller/ledger/log_process.go (3 hunks)
  • internal/controller/ledger/log_process_test.go (5 hunks)
  • internal/controller/ledger/parameters.go (1 hunks)
  • internal/controller/ledger/store.go (3 hunks)
  • internal/controller/ledger/store_generated_test.go (6 hunks)
  • internal/errors.go (2 hunks)
  • internal/log.go (13 hunks)
  • internal/schema.go (1 hunks)
  • internal/storage/bucket/default_bucket.go (1 hunks)
  • internal/storage/bucket/migrations/42-add-schema/up.sql (1 hunks)
  • internal/storage/ledger/accounts.go (3 hunks)
  • internal/storage/ledger/accounts_test.go (7 hunks)
  • internal/storage/ledger/balances_test.go (3 hunks)
  • internal/storage/ledger/moves_test.go (2 hunks)
  • internal/storage/ledger/resource_schemas.go (1 hunks)
  • internal/storage/ledger/schema.go (1 hunks)
  • internal/storage/ledger/schema_test.go (1 hunks)
  • internal/storage/ledger/store.go (1 hunks)
  • internal/storage/ledger/transactions.go (2 hunks)
  • internal/storage/ledger/transactions_test.go (17 hunks)
  • internal/storage/ledger/volumes_test.go (2 hunks)
  • pkg/client/README.md (1 hunks)
  • pkg/client/docs/models/components/dotself.md (1 hunks)
  • pkg/client/docs/models/components/v2chartaccountmetadata.md (1 hunks)
  • pkg/client/docs/models/components/v2chartaccountrules.md (1 hunks)
  • pkg/client/docs/models/components/v2chartsegment.md (1 hunks)
  • pkg/client/docs/models/components/v2errorsenum.md (1 hunks)
  • pkg/client/docs/models/components/v2log.md (1 hunks)
  • pkg/client/docs/models/components/v2logtype.md (1 hunks)
  • pkg/client/docs/models/components/v2schema.md (1 hunks)
  • pkg/client/docs/models/components/v2schemadata.md (1 hunks)
  • pkg/client/docs/models/components/v2schemaresponse.md (1 hunks)
  • pkg/client/docs/models/components/v2schemascursor.md (1 hunks)
  • pkg/client/docs/models/components/v2schemascursorresponse.md (1 hunks)
  • pkg/client/docs/models/operations/order.md (1 hunks)
  • pkg/client/docs/models/operations/queryparamorder.md (1 hunks)
  • pkg/client/docs/models/operations/sort.md (1 hunks)
  • pkg/client/docs/models/operations/v2addmetadataontransactionrequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2addmetadatatoaccountrequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2createbulkrequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2createtransactionrequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2getschemarequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2getschemaresponse.md (1 hunks)
  • pkg/client/docs/models/operations/v2insertschemarequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2insertschemaresponse.md (1 hunks)
  • pkg/client/docs/models/operations/v2listschemasrequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2listschemasresponse.md (1 hunks)
  • pkg/client/docs/models/operations/v2listtransactionsrequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2reverttransactionrequest.md (1 hunks)
  • pkg/client/docs/sdks/v2/README.md (7 hunks)
  • pkg/client/models/components/v2chartaccountmetadata.go (1 hunks)
  • pkg/client/models/components/v2chartaccountrules.go (1 hunks)
  • pkg/client/models/components/v2chartsegment.go (1 hunks)
  • pkg/client/models/components/v2errorsenum.go (2 hunks)
  • pkg/client/models/components/v2log.go (4 hunks)
  • pkg/client/models/components/v2schema.go (1 hunks)
  • pkg/client/models/components/v2schemadata.go (1 hunks)
  • pkg/client/models/components/v2schemaresponse.go (1 hunks)
  • pkg/client/models/components/v2schemascursor.go (1 hunks)
  • pkg/client/models/components/v2schemascursorresponse.go (1 hunks)
  • pkg/client/models/operations/v2addmetadataontransaction.go (2 hunks)
  • pkg/client/models/operations/v2addmetadatatoaccount.go (2 hunks)
  • pkg/client/models/operations/v2createbulk.go (2 hunks)
  • pkg/client/models/operations/v2createtransaction.go (2 hunks)
  • pkg/client/models/operations/v2getschema.go (1 hunks)
  • pkg/client/models/operations/v2insertschema.go (1 hunks)
  • pkg/client/models/operations/v2listschemas.go (1 hunks)
  • pkg/client/models/operations/v2listtransactions.go (3 hunks)
  • pkg/client/models/operations/v2reverttransaction.go (2 hunks)
  • pkg/client/v2.go (1 hunks)
  • pkg/events/events.go (1 hunks)
  • pkg/events/message.go (1 hunks)
  • test/e2e/api_schema_test.go (1 hunks)
  • test/e2e/api_transactions_list_test.go (1 hunks)
✅ Files skipped from review due to trivial changes (5)
  • pkg/client/docs/models/components/dotself.md
  • pkg/client/docs/models/components/v2schemascursorresponse.md
  • pkg/client/docs/models/operations/v2reverttransactionrequest.md
  • pkg/client/docs/models/operations/sort.md
  • pkg/client/docs/models/components/v2schema.md
🚧 Files skipped from review as they are similar to previous changes (37)
  • pkg/client/docs/models/operations/v2addmetadatatoaccountrequest.md
  • internal/api/v2/query.go
  • pkg/client/docs/models/components/v2chartaccountrules.md
  • pkg/client/models/operations/v2reverttransaction.go
  • pkg/client/models/operations/v2addmetadatatoaccount.go
  • internal/controller/ledger/listener.go
  • cmd/docs_events.go
  • internal/storage/ledger/store.go
  • pkg/client/models/components/v2schemaresponse.go
  • pkg/client/models/components/v2schemascursorresponse.go
  • pkg/client/docs/models/operations/v2insertschemaresponse.md
  • internal/api/v2/controllers_schema_read_test.go
  • internal/controller/ledger/controller.go
  • internal/controller/ledger/listener_generated_test.go
  • pkg/client/docs/models/operations/v2getschemarequest.md
  • pkg/client/docs/models/operations/queryparamorder.md
  • internal/storage/ledger/schema.go
  • internal/api/v2/controllers_schema_list.go
  • internal/api/bulking/mocks_ledger_controller_test.go
  • pkg/client/docs/models/operations/v2createtransactionrequest.md
  • pkg/client/docs/models/operations/v2insertschemarequest.md
  • pkg/client/models/operations/v2addmetadataontransaction.go
  • internal/storage/bucket/default_bucket.go
  • pkg/client/docs/models/components/v2errorsenum.md
  • internal/controller/ledger/errors.go
  • pkg/client/docs/models/operations/order.md
  • test/e2e/api_transactions_list_test.go
  • internal/bus/listener.go
  • internal/storage/bucket/migrations/42-add-schema/up.sql
  • pkg/client/models/components/v2schemascursor.go
  • pkg/client/models/operations/v2listschemas.go
  • pkg/client/models/operations/v2createtransaction.go
  • pkg/client/docs/models/operations/v2getschemaresponse.md
  • internal/controller/ledger/store.go
  • pkg/client/docs/models/components/v2chartaccountmetadata.md
  • pkg/client/docs/models/components/v2schemascursor.md
  • pkg/client/docs/models/components/v2log.md
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-04-29T11:24:28.923Z
Learnt from: gfyrag
Repo: formancehq/ledger PR: 892
File: internal/controller/ledger/controller_default.go:196-196
Timestamp: 2025-04-29T11:24:28.923Z
Learning: In the ledger Import function, it's critical to maintain proper log ID tracking by updating lastLogID with the current log.ID after each processed log, rather than setting it to nil. This ensures the system can properly validate the ordering of logs and prevent duplicate or out-of-order processing, which is essential for maintaining data integrity in the ledger.

Applied to files:

  • internal/controller/ledger/controller_default.go
  • internal/README.md
🧬 Code graph analysis (32)
internal/controller/ledger/controller_with_traces.go (5)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (135-138)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/tracing/tracing.go (1)
  • TraceWithMetric (101-129)
internal/schema.go (1)
  • Schema (15-21)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (26-29)
pkg/client/models/operations/v2getschema.go (1)
pkg/client/models/components/v2schemaresponse.go (1)
  • V2SchemaResponse (5-8)
internal/api/common/errors.go (1)
internal/controller/ledger/errors.go (3)
  • ErrSchemaNotSpecified (288-290)
  • ErrSchemaNotFound (249-252)
  • ErrSchemaValidationError (267-270)
pkg/events/message.go (3)
internal/schema.go (1)
  • Schema (15-21)
internal/log.go (1)
  • UpdatedSchema (416-418)
pkg/events/events.go (3)
  • EventApp (5-5)
  • EventVersion (4-4)
  • EventTypeUpdatedSchema (11-11)
internal/api/v2/views.go (1)
internal/schema.go (1)
  • Schema (15-21)
internal/api/bulking/bulker_test.go (1)
internal/machine/vm/run.go (1)
  • Run (57-87)
pkg/client/models/components/v2schemadata.go (1)
pkg/client/models/components/v2chartsegment.go (1)
  • V2ChartSegment (13-19)
internal/schema.go (2)
internal/chart.go (1)
  • ChartOfAccounts (40-40)
internal/errors.go (1)
  • ErrInvalidSchema (91-93)
internal/api/bulking/bulker.go (1)
internal/api/bulking/elements.go (3)
  • Bulk (22-22)
  • BulkElementResult (73-78)
  • BulkElement (24-28)
internal/storage/ledger/resource_schemas.go (2)
internal/storage/common/resource.go (6)
  • EntitySchema (60-62)
  • RepositoryHandlerBuildContext (74-77)
  • ResourceQuery (410-416)
  • ConvertOperatorToSQL (19-35)
  • JoinCondition (37-40)
  • RepositoryHandler (93-99)
internal/storage/common/schema.go (3)
  • Field (28-32)
  • NewStringField (65-67)
  • NewDateField (70-72)
pkg/client/models/operations/v2listtransactions.go (1)
pkg/client/models/operations/v2listschemas.go (1)
  • Order (37-37)
internal/storage/ledger/transactions.go (2)
internal/controller/ledger/store.go (1)
  • Store (29-67)
internal/storage/ledger/store.go (1)
  • Store (25-50)
internal/controller/ledger/controller_with_events.go (4)
internal/controller/ledger/controller.go (2)
  • UpdateSchema (135-138)
  • Controller (21-89)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/log.go (1)
  • UpdatedSchema (416-418)
pkg/events/message.go (1)
  • UpdatedSchema (77-80)
internal/storage/ledger/transactions_test.go (3)
internal/transaction.go (1)
  • NewTransaction (228-232)
internal/posting.go (1)
  • NewPosting (26-33)
internal/controller/ledger/controller.go (1)
  • RevertTransaction (108-113)
internal/controller/ledger/log_process_test.go (4)
internal/controller/ledger/store.go (1)
  • Store (29-67)
internal/schema.go (1)
  • Schema (15-21)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/log.go (1)
  • CreatedTransaction (213-216)
pkg/client/models/operations/v2createbulk.go (1)
pkg/client/models/components/v2bulkelement.go (1)
  • V2BulkElement (21-28)
internal/storage/ledger/accounts.go (3)
internal/controller/ledger/store.go (1)
  • Store (29-67)
internal/schema.go (1)
  • Schema (15-21)
internal/account.go (1)
  • Account (16-26)
pkg/client/models/operations/v2insertschema.go (1)
pkg/client/models/components/v2schemadata.go (1)
  • V2SchemaData (6-9)
internal/controller/ledger/log_process.go (8)
internal/controller/ledger/store.go (1)
  • Store (29-67)
internal/storage/ledger/store.go (1)
  • Store (25-50)
internal/schema.go (1)
  • Schema (15-21)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/log.go (3)
  • Log (89-102)
  • NewLog (194-199)
  • ComputeIdempotencyHash (457-466)
internal/storage/ledger/logs.go (1)
  • Log (20-26)
internal/controller/ledger/errors.go (3)
  • ErrNotFound (14-14)
  • ErrSchemaNotFound (249-252)
  • ErrSchemaNotSpecified (288-290)
internal/api/common/errors.go (1)
  • ErrSchemaNotSpecified (28-28)
pkg/client/models/components/v2schema.go (2)
pkg/client/models/components/v2schemadata.go (1)
  • V2SchemaData (6-9)
pkg/client/internal/utils/json.go (2)
  • MarshalJSON (21-115)
  • UnmarshalJSON (117-251)
internal/storage/ledger/schema_test.go (1)
internal/schema.go (2)
  • NewSchema (23-33)
  • SchemaData (11-13)
internal/api/v1/mocks_ledger_controller_test.go (5)
internal/schema.go (1)
  • Schema (15-21)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (26-29)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (135-138)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
pkg/events/message.go (1)
  • UpdatedSchema (77-80)
internal/api/v2/mocks_ledger_controller_test.go (5)
internal/schema.go (1)
  • Schema (15-21)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (26-29)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (135-138)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
pkg/events/message.go (1)
  • UpdatedSchema (77-80)
internal/log.go (4)
internal/storage/ledger/logs.go (1)
  • Log (20-26)
internal/schema.go (1)
  • Schema (15-21)
internal/transaction.go (2)
  • Transaction (37-51)
  • Transaction (53-60)
pkg/events/message.go (1)
  • UpdatedSchema (77-80)
pkg/client/models/components/v2chartsegment.go (3)
pkg/client/models/components/v2chartaccountrules.go (1)
  • V2ChartAccountRules (5-6)
pkg/client/models/components/v2chartaccountmetadata.go (1)
  • V2ChartAccountMetadata (5-7)
pkg/client/internal/utils/json.go (2)
  • MarshalJSON (21-115)
  • UnmarshalJSON (117-251)
internal/chart_test.go (2)
internal/chart.go (6)
  • ChartOfAccounts (40-40)
  • ChartVariableSegment (27-32)
  • ChartSegment (21-25)
  • ChartAccount (16-19)
  • ChartAccountRules (10-10)
  • ChartAccountMetadata (12-14)
internal/errors.go (1)
  • ErrInvalidAccount (103-108)
internal/controller/ledger/controller_with_too_many_client_handling.go (5)
internal/controller/ledger/controller.go (2)
  • UpdateSchema (135-138)
  • Controller (21-89)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/log.go (2)
  • Log (89-102)
  • UpdatedSchema (416-418)
internal/schema.go (1)
  • Schema (15-21)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (26-29)
internal/api/v2/controllers_schema_read.go (1)
internal/api/common/errors.go (1)
  • HandleCommonErrors (38-49)
internal/api/v2/controllers_schema_insert_test.go (2)
internal/controller/ledger/errors.go (1)
  • ErrSchemaValidationError (267-270)
internal/controller/ledger/controller.go (1)
  • UpdateSchema (135-138)
internal/controller/ledger/store_generated_test.go (2)
internal/schema.go (1)
  • Schema (15-21)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (26-29)
internal/controller/ledger/controller_default_test.go (5)
internal/schema.go (2)
  • Schema (15-21)
  • SchemaData (11-13)
internal/log.go (2)
  • Log (89-102)
  • UpdatedSchemaLogType (25-25)
internal/controller/ledger/controller.go (2)
  • UpdateSchema (135-138)
  • RunScript (91-91)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/machine/vm/run.go (1)
  • RunScript (14-19)
internal/chart.go (2)
internal/storage/ledger/logs.go (1)
  • RawMessage (38-38)
internal/errors.go (1)
  • ErrInvalidAccount (103-108)
🪛 markdownlint-cli2 (0.18.1)
pkg/client/docs/models/operations/v2listtransactionsrequest.md

13-13: Spaces inside emphasis markers

(MD037, no-space-in-emphasis)


13-13: Spaces inside emphasis markers

(MD037, no-space-in-emphasis)

pkg/client/docs/sdks/v2/README.md

250-250: Hard tabs
Column: 1

(MD010, no-hard-tabs)


251-251: Hard tabs
Column: 1

(MD010, no-hard-tabs)


252-252: Hard tabs
Column: 1

(MD010, no-hard-tabs)


253-253: Hard tabs
Column: 1

(MD010, no-hard-tabs)


254-254: Hard tabs
Column: 1

(MD010, no-hard-tabs)


255-255: Hard tabs
Column: 1

(MD010, no-hard-tabs)


321-321: Hard tabs
Column: 1

(MD010, no-hard-tabs)


322-322: Hard tabs
Column: 1

(MD010, no-hard-tabs)


323-323: Hard tabs
Column: 1

(MD010, no-hard-tabs)


324-324: Hard tabs
Column: 1

(MD010, no-hard-tabs)


325-325: Hard tabs
Column: 1

(MD010, no-hard-tabs)


326-326: Hard tabs
Column: 1

(MD010, no-hard-tabs)


381-381: Hard tabs
Column: 1

(MD010, no-hard-tabs)


382-382: Hard tabs
Column: 1

(MD010, no-hard-tabs)


383-383: Hard tabs
Column: 1

(MD010, no-hard-tabs)


384-384: Hard tabs
Column: 1

(MD010, no-hard-tabs)


385-385: Hard tabs
Column: 1

(MD010, no-hard-tabs)


386-386: Hard tabs
Column: 1

(MD010, no-hard-tabs)

docs/api/README.md

357-357: Blank line inside blockquote

(MD028, no-blanks-blockquote)


403-403: Blank line inside blockquote

(MD028, no-blanks-blockquote)


461-461: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4

(MD001, heading-increment)


470-470: Blank line inside blockquote

(MD028, no-blanks-blockquote)


5615-5615: Multiple headings with the same content

(MD024, no-duplicate-heading)


5617-5617: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


5633-5633: Multiple headings with the same content

(MD024, no-duplicate-heading)


5659-5659: Multiple headings with the same content

(MD024, no-duplicate-heading)


5690-5690: Multiple headings with the same content

(MD024, no-duplicate-heading)


5718-5718: Multiple headings with the same content

(MD024, no-duplicate-heading)


5750-5750: Multiple headings with the same content

(MD024, no-duplicate-heading)


5784-5784: Multiple headings with the same content

(MD024, no-duplicate-heading)


5823-5823: Multiple headings with the same content

(MD024, no-duplicate-heading)


5860-5860: Multiple headings with the same content

(MD024, no-duplicate-heading)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/controller/ledger/controller_default_test.go (1)

25-88: Fix InsertLog DoAndReturn return type to avoid gomock runtime panics

In testCreateTransaction, the InsertLog expectations for the inserted-schema log (lines 72-80) and the new-transaction log (lines 131-138) use:

DoAndReturn(func(_ context.Context, log *ledger.Log) any {
    log.ID = pointer.For(uint64(0))
    return log
})

Elsewhere in this file (e.g., Revert/Save/Delete transaction metadata tests), InsertLog’s DoAndReturn closures return error, which matches the typical Store.InsertLog(ctx context.Context, log *ledger.Log) error signature. Returning any here is very likely to cause gomock’s reflection-based call to fail at runtime because interface{} is not assignable to error.

Update both InsertLog mocks to return error instead, e.g.:

-   DoAndReturn(func(_ context.Context, log *ledger.Log) any {
-       log.ID = pointer.For(uint64(0))
-       return log
-   })
+   DoAndReturn(func(_ context.Context, log *ledger.Log) error {
+       log.ID = pointer.For(uint64(0))
+       return nil
+   })

Apply the same change to the NewTransactionLogType InsertLog expectation at lines 131–138 for consistency with the rest of the tests.

Also applies to: 131-138

♻️ Duplicate comments (5)
internal/README.md (1)

256-256: Regex inconsistency between chart segments and account address validation persists.

Line 256 documents ChartSegmentRegexp = regexp.MustCompile(\^($|.)?[a-zA-Z0-9_-]+$`), which permits hyphens (-) in segment names. However, the HTTP API account-address regex documented elsewhere (^\w+(:\w+)*$) only allows word characters ([A-Za-z0-9_]`). This means chart segments defined with hyphens will never match valid account addresses, creating an inconsistency between schema configuration and address validation.

This was flagged in previous reviews and remains unresolved. Pick one consistent rule:

  • Either disallow - from ChartSegmentRegexp (change to ^(\$|\.)?[A-Za-z0-9_]+$)
  • Or broaden the account-address regex to accept hyphens (e.g., ^[A-Za-z0-9_-]+(:[A-Za-z0-9_-]+)*$)

Then update all related validation code, tests, and documentation accordingly.

#!/bin/bash
# Description: Verify the regex implementations in chart.go and account validation.

# Find and display ValidateSegment implementation
echo "=== ValidateSegment implementation ==="
rg -A 5 'func ValidateSegment' internal/

# Find and display account address validation rules
echo -e "\n=== Account address validation (in API or validators) ==="
rg -n 'account.*regex|address.*regex|^\s*\w+.*\(\:\w+' internal/ --type go -C 3

# Find all uses of ChartSegmentRegexp
echo -e "\n=== ChartSegmentRegexp usage ==="
rg -n 'ChartSegmentRegexp' internal/ --type go -C 2
internal/storage/bucket/migrations/42-add-schema/up.sql (1)

29-47: Replace manual JSON string building in compute_hash with JSONB constructors.

Building the hash preimage JSON via string concatenation and encode(r.memento, 'escape') is brittle and can generate invalid or unexpected JSON (e.g. embedded quotes, newlines, or non‑UTF8 bytes). It also makes future changes (like adding fields) error‑prone.

You can construct the JSON payload in a type‑safe way and still keep the same hash semantics by using jsonb_build_object plus an optional schemaVersion object, e.g.:

-	select '{' ||
-	       '"type":"' || r.type || '",' ||
-	       '"data":' || encode(r.memento, 'escape') || ',' ||
-	       '"date":"' || (to_json(r.date::timestamp)#>>'{}') || 'Z",' ||
-	       '"idempotencyKey":"' || coalesce(r.idempotency_key, '') || '",' ||
-	       '"id":0,' ||
-	       '"hash":null' into marshalledAsJSON;
-	if r.schema_version is not null then
-		marshalledAsJSON := marshalledAsJSON || ',"schemaVersion":"' || r.schema_version || '"';
-	end if;
-	marshalledAsJSON := marshalledAsJSON || '}';
+	select (
+		jsonb_build_object(
+			'type', r.type,
+			'data', r.memento,
+			'date', to_jsonb(r.date::timestamp)#>>'{}' || 'Z',
+			'idempotencyKey', coalesce(r.idempotency_key, ''),
+			'id', 0,
+			'hash', null
+		) ||
+		case
+			when r.schema_version is not null
+				then jsonb_build_object('schemaVersion', r.schema_version)
+			else '{}'::jsonb
+		end
+	)::text
+	into marshalledAsJSON;

This keeps marshalledAsJSON as text but relies on PostgreSQL’s JSON machinery for correct escaping and ordering, and uses to_jsonb(... )#>>'{}' for an ISO‑8601/RFC3339 timestamp string. Adjust r.memento’s type handling if it is not already JSON/JSONB. Based on learnings, this pattern avoids DateStyle‑dependent text formatting and unsafe string concatenation.

pkg/client/docs/sdks/v2/README.md (1)

246-256: Replace hard tabs with spaces in new Go examples (MD010)

The three newly added Go examples (InsertSchema, GetSchema, ListSchemas) still use hard tabs in their import blocks, which triggers markdownlint MD010 and is inconsistent with the rest of the README examples.

Recommend replacing the leading tab characters with spaces (e.g., 4 spaces) in those blocks, e.g.:

-import(
-	"context"
-	"os"
-	"github.com/formancehq/ledger/pkg/client/models/components"
-	"github.com/formancehq/ledger/pkg/client"
-	"github.com/formancehq/ledger/pkg/client/models/operations"
-	"log"
-)
+import(
+    "context"
+    "os"
+    "github.com/formancehq/ledger/pkg/client/models/components"
+    "github.com/formancehq/ledger/pkg/client"
+    "github.com/formancehq/ledger/pkg/client/models/operations"
+    "log"
+)

Apply the same pattern to the InsertSchema, GetSchema, and ListSchemas examples to satisfy MD010 and keep examples consistent.

Also applies to: 320-328, 380-388

internal/log.go (1)

449-454: Critical: json.Unmarshal with pointer-to-interface may cause type erasure.

Passing &payload (a *interface{}) to json.Unmarshal causes it to replace the concrete struct pointer (e.g., &CreatedTransaction{}) with a generic map[string]interface{}. This breaks the subsequent .Elem().Interface().(LogPayload) cast.

This issue was flagged in a previous review. Please verify it was addressed, or apply this fix:

-	err := json.Unmarshal(data, &payload)
+	err := json.Unmarshal(data, payload)

Run the following to verify the current behavior:

#!/bin/bash
# Check if HydrateLog uses &payload or payload
rg -n 'json\.Unmarshal.*&payload' internal/log.go
docs/api/README.md (1)

5603-5868: Markdownlint issues in auto-generated schema section.

Static analysis still flags MD024 (duplicate ### Properties headings) and MD036 (emphasis as heading) in this section. These were previously flagged and marked as addressed.

Please verify the markdownlint configuration or CI is updated to handle these auto-generated sections, or confirm that the generator/template was updated to produce unique headings.

#!/bin/bash
# Check if markdownlint config exists and handles these rules
fd -t f '.markdownlint' --exec cat {}
🧹 Nitpick comments (2)
internal/controller/ledger/controller.go (1)

83-88: Controller schema API surface looks coherent with the rest of the controller.

The new InsertSchema, GetSchema, and ListSchemas methods fit naturally alongside existing account/transaction/log APIs and use the right types (Parameters[InsertSchema], *ledger.Schema, cursor over ledger.Schema).

Consider tweaking the InsertSchema comment to clarify that it inserts a new schema version (chart of accounts) rather than “updates” it, to avoid ambiguity about mutability of past versions.

internal/controller/ledger/controller_with_traces.go (1)

474-539: Align InsertSchema trace span name with method name

InsertSchema currently uses the span name "InsertedSchema" in TraceWithMetric, while other methods generally use a span equal to the method name (e.g., "CreateTransaction", "RevertTransaction", "GetSchema", "ListSchemas").

For consistency in tracing/observability, consider:

-    _, err = tracing.TraceWithMetric(
-        ctx,
-        "InsertedSchema",
+    _, err = tracing.TraceWithMetric(
+        ctx,
+        "InsertSchema",
         c.tracer,
         c.insertSchemaHistogram,

This keeps the span naming convention aligned with the method surface.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 42ba087 and 18de8a9.

⛔ Files ignored due to path filters (4)
  • docs/events/InsertedSchema.json is excluded by !**/*.json
  • openapi.yaml is excluded by !**/*.yaml
  • openapi/v2.yaml is excluded by !**/*.yaml
  • pkg/client/.speakeasy/gen.lock is excluded by !**/*.lock, !**/*.lock
📒 Files selected for processing (29)
  • cmd/docs_events.go (1 hunks)
  • docs/api/README.md (13 hunks)
  • internal/README.md (51 hunks)
  • internal/api/bulking/mocks_ledger_controller_test.go (3 hunks)
  • internal/api/common/mocks_ledger_controller_test.go (3 hunks)
  • internal/api/v1/mocks_ledger_controller_test.go (3 hunks)
  • internal/api/v2/controllers_schema_insert.go (1 hunks)
  • internal/api/v2/controllers_schema_insert_test.go (1 hunks)
  • internal/api/v2/mocks_ledger_controller_test.go (3 hunks)
  • internal/bus/listener.go (1 hunks)
  • internal/controller/ledger/controller.go (2 hunks)
  • internal/controller/ledger/controller_default.go (12 hunks)
  • internal/controller/ledger/controller_default_test.go (8 hunks)
  • internal/controller/ledger/controller_generated_test.go (3 hunks)
  • internal/controller/ledger/controller_with_events.go (1 hunks)
  • internal/controller/ledger/controller_with_too_many_client_handling.go (2 hunks)
  • internal/controller/ledger/controller_with_traces.go (3 hunks)
  • internal/controller/ledger/listener.go (1 hunks)
  • internal/controller/ledger/listener_generated_test.go (1 hunks)
  • internal/log.go (13 hunks)
  • internal/storage/bucket/migrations/42-add-schema/up.sql (1 hunks)
  • internal/storage/ledger/schema_test.go (1 hunks)
  • pkg/client/README.md (1 hunks)
  • pkg/client/docs/models/components/v2logtype.md (1 hunks)
  • pkg/client/docs/sdks/v2/README.md (7 hunks)
  • pkg/client/models/components/v2log.go (4 hunks)
  • pkg/client/v2.go (1 hunks)
  • pkg/events/events.go (1 hunks)
  • pkg/events/message.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (10)
  • cmd/docs_events.go
  • internal/api/v2/controllers_schema_insert_test.go
  • pkg/client/models/components/v2log.go
  • pkg/events/events.go
  • internal/controller/ledger/controller_with_events.go
  • internal/api/v2/controllers_schema_insert.go
  • pkg/client/README.md
  • pkg/events/message.go
  • internal/api/common/mocks_ledger_controller_test.go
  • internal/storage/ledger/schema_test.go
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-11-28T10:06:01.381Z
Learnt from: gfyrag
Repo: formancehq/ledger PR: 1167
File: internal/storage/bucket/migrations/43-fix-missing-inserted-at-in-log-data/up.sql:23-23
Timestamp: 2025-11-28T10:06:01.381Z
Learning: In PostgreSQL migrations for the Formance ledger, to ensure ISO 8601/RFC3339 date formatting when setting JSONB fields, use the pattern `to_jsonb(date)#>>'{}'` to extract the timestamp as a properly formatted string. This leverages JSONB's built-in ISO 8601 encoding. Using `date::text` would depend on the server's DateStyle setting and doesn't guarantee ISO 8601 format.

Applied to files:

  • internal/storage/bucket/migrations/42-add-schema/up.sql
📚 Learning: 2025-04-29T11:24:28.923Z
Learnt from: gfyrag
Repo: formancehq/ledger PR: 892
File: internal/controller/ledger/controller_default.go:196-196
Timestamp: 2025-04-29T11:24:28.923Z
Learning: In the ledger Import function, it's critical to maintain proper log ID tracking by updating lastLogID with the current log.ID after each processed log, rather than setting it to nil. This ensures the system can properly validate the ordering of logs and prevent duplicate or out-of-order processing, which is essential for maintaining data integrity in the ledger.

Applied to files:

  • internal/controller/ledger/controller_default.go
  • internal/README.md
🧬 Code graph analysis (9)
internal/controller/ledger/listener.go (3)
internal/log.go (1)
  • InsertedSchema (416-418)
pkg/events/message.go (1)
  • InsertedSchema (77-80)
internal/schema.go (1)
  • Schema (15-21)
internal/bus/listener.go (4)
internal/log.go (1)
  • InsertedSchema (416-418)
pkg/events/message.go (2)
  • InsertedSchema (77-80)
  • NewEventInsertedSchema (82-90)
internal/schema.go (1)
  • Schema (15-21)
pkg/events/events.go (1)
  • EventTypeInsertedSchema (11-11)
internal/controller/ledger/listener_generated_test.go (4)
internal/log.go (1)
  • InsertedSchema (416-418)
pkg/events/message.go (1)
  • InsertedSchema (77-80)
internal/schema.go (1)
  • Schema (15-21)
internal/storage/ledger/main_test.go (1)
  • T (75-79)
internal/controller/ledger/controller.go (4)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
pkg/events/message.go (1)
  • InsertedSchema (77-80)
internal/schema.go (2)
  • Schema (15-21)
  • SchemaData (11-13)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (26-29)
internal/api/bulking/mocks_ledger_controller_test.go (4)
internal/schema.go (1)
  • Schema (15-21)
internal/controller/ledger/controller.go (1)
  • InsertSchema (135-138)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (26-29)
internal/api/v2/mocks_ledger_controller_test.go (5)
internal/schema.go (1)
  • Schema (15-21)
internal/controller/ledger/controller.go (1)
  • InsertSchema (135-138)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/storage/ledger/logs.go (1)
  • Log (20-26)
pkg/events/message.go (1)
  • InsertedSchema (77-80)
internal/controller/ledger/controller_generated_test.go (5)
internal/schema.go (1)
  • Schema (15-21)
internal/controller/ledger/controller.go (1)
  • InsertSchema (135-138)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
pkg/events/message.go (1)
  • InsertedSchema (77-80)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (26-29)
internal/api/v1/mocks_ledger_controller_test.go (5)
internal/schema.go (1)
  • Schema (15-21)
internal/controller/ledger/controller.go (1)
  • InsertSchema (135-138)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
pkg/events/message.go (1)
  • InsertedSchema (77-80)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (26-29)
internal/log.go (3)
internal/schema.go (1)
  • Schema (15-21)
internal/transaction.go (2)
  • Transaction (37-51)
  • Transaction (53-60)
pkg/events/message.go (1)
  • InsertedSchema (77-80)
🪛 markdownlint-cli2 (0.18.1)
pkg/client/docs/sdks/v2/README.md

250-250: Hard tabs
Column: 1

(MD010, no-hard-tabs)


251-251: Hard tabs
Column: 1

(MD010, no-hard-tabs)


252-252: Hard tabs
Column: 1

(MD010, no-hard-tabs)


253-253: Hard tabs
Column: 1

(MD010, no-hard-tabs)


254-254: Hard tabs
Column: 1

(MD010, no-hard-tabs)


255-255: Hard tabs
Column: 1

(MD010, no-hard-tabs)


321-321: Hard tabs
Column: 1

(MD010, no-hard-tabs)


322-322: Hard tabs
Column: 1

(MD010, no-hard-tabs)


323-323: Hard tabs
Column: 1

(MD010, no-hard-tabs)


324-324: Hard tabs
Column: 1

(MD010, no-hard-tabs)


325-325: Hard tabs
Column: 1

(MD010, no-hard-tabs)


326-326: Hard tabs
Column: 1

(MD010, no-hard-tabs)


381-381: Hard tabs
Column: 1

(MD010, no-hard-tabs)


382-382: Hard tabs
Column: 1

(MD010, no-hard-tabs)


383-383: Hard tabs
Column: 1

(MD010, no-hard-tabs)


384-384: Hard tabs
Column: 1

(MD010, no-hard-tabs)


385-385: Hard tabs
Column: 1

(MD010, no-hard-tabs)


386-386: Hard tabs
Column: 1

(MD010, no-hard-tabs)

docs/api/README.md

357-357: Blank line inside blockquote

(MD028, no-blanks-blockquote)


403-403: Blank line inside blockquote

(MD028, no-blanks-blockquote)


461-461: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4

(MD001, heading-increment)


470-470: Blank line inside blockquote

(MD028, no-blanks-blockquote)


5615-5615: Multiple headings with the same content

(MD024, no-duplicate-heading)


5617-5617: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


5633-5633: Multiple headings with the same content

(MD024, no-duplicate-heading)


5659-5659: Multiple headings with the same content

(MD024, no-duplicate-heading)


5690-5690: Multiple headings with the same content

(MD024, no-duplicate-heading)


5718-5718: Multiple headings with the same content

(MD024, no-duplicate-heading)


5750-5750: Multiple headings with the same content

(MD024, no-duplicate-heading)


5784-5784: Multiple headings with the same content

(MD024, no-duplicate-heading)


5823-5823: Multiple headings with the same content

(MD024, no-duplicate-heading)


5860-5860: Multiple headings with the same content

(MD024, no-duplicate-heading)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: GoReleaser
  • GitHub Check: Dirty
  • GitHub Check: Tests
🔇 Additional comments (30)
internal/README.md (2)

1159-1160: All LogPayload implementations include required schema validation methods.

Verified that the LogPayload interface in internal/log.go:201-205 includes NeedsSchema() bool and ValidateWithSchema(schema Schema) error. All five implementations are present with both methods:

  • CreatedTransaction (lines 218, 221)
  • SavedMetadata (lines 272, 275)
  • DeletedMetadata (lines 324, 327)
  • RevertedTransaction (lines 375, 378)
  • InsertedSchema (lines 420, 423)

1075-1075: File reference correction needed: internal/log.go line 101, not internal/README.md line 1075.

SchemaVersion field is properly initialized and used throughout the transaction pipeline:

  1. Extraction: SchemaVersion extracted from request query parameters (internal/api/v2/query.go:14).
  2. Propagation: Passed through Parameters struct to log_process.go and assigned to log at line 112: log.SchemaVersion = parameters.SchemaVersion.
  3. Validation: Schema is fetched using the provided SchemaVersion at line 75, and the log is validated with the schema at line 115 before insertion. If no schema version is specified, validation falls back to the latest schema version.

All initialization and propagation concerns are correctly addressed in the implementation.

internal/controller/ledger/listener_generated_test.go (1)

67-77: InsertedSchema mock wiring matches Listener interface.

The mock and recorder methods for InsertedSchema follow the same pattern as existing listener methods and correctly mirror the Listener.InsertedSchema signature.

pkg/client/v2.go (3)

656-849: InsertSchema client method is consistent with existing V2 patterns.

URL, HTTP verb, body serialization (V2SchemaData) and 204/no‑body success handling match how other write operations (e.g. CreateLedger) are implemented.


851-1057: GetSchema client method aligns with read‑style V2 operations.

GET /v2/{ledger}/schema/{version} with 200 + JSON decoded into components.V2SchemaResponse mirrors other simple GET endpoints (GetLedger, GetTransaction) and reuses the standard retry and hooks flow.


1059-1269: ListSchemas client method correctly models cursor‑based listing.

GET /v2/{ledger}/schema, query‑param population, and unmarshalling into components.V2SchemasCursorResponse are in line with other cursor‑based list methods (accounts, transactions, logs).

pkg/client/docs/models/components/v2logtype.md (1)

11-12: Doc enum entry for INSERTED_SCHEMA is correct and aligned with code.

The V2LogTypeInsertedSchemaINSERTED_SCHEMA row matches the new log type introduced in the events/log pipeline and keeps the client docs coherent.

internal/controller/ledger/listener.go (1)

12-18: InsertedSchema added to Listener with consistent signature.

The new InsertedSchema method uses the same ledger string convention as other methods and cleanly exposes schema insert events to listeners.

internal/bus/listener.go (1)

29-35: InsertedSchema event publishing matches existing listener patterns.

LedgerListener.InsertedSchema correctly wraps the schema and ledger into events.InsertedSchema and publishes on EventTypeInsertedSchema, consistent with the other event emitters in this type.

internal/controller/ledger/controller_with_too_many_client_handling.go (1)

127-165: Schema controller wrapper methods correctly integrate with retry handling.

InsertSchema, GetSchema, and ListSchemas follow the same handleRetry pattern as existing methods, preserve return types, and use the new common.PaginatedQuery[any] / bunpaginate.Cursor[ledger.Schema] types appropriately.

internal/controller/ledger/controller.go (1)

135-138: InsertSchema input DTO aligns with internal schema model.

The InsertSchema struct with Version and Data ledger.SchemaData matches internal.Schema/SchemaData and cleanly separates version from chart payload.

pkg/client/docs/sdks/v2/README.md (2)

11-13: Schema operations documentation looks consistent and complete

InsertSchema, GetSchema, and ListSchemas are correctly wired into the operations list, with examples, parameters, responses, and error tables matching the rest of the V2 docs surface. No issues from a docs/API-shape perspective.

Also applies to: 240-310, 311-370, 371-429


644-645: SchemaVersion usage in examples is coherent with new schema API

The added SchemaVersion: client.String("v1.0.0") fields in the CreateBulk, AddMetadataToAccount, CreateTransaction, AddMetadataOnTransaction, and RevertTransaction examples consistently show how callers bind operations to a specific schema version, aligning with the new schema management API.

Also applies to: 901-902, 1215-1216, 1377-1378, 1505-1506

internal/api/bulking/mocks_ledger_controller_test.go (1)

214-227: Schema-related bulking mocks correctly mirror controller interface

The new GetSchema, InsertSchema, and ListSchemas mock methods and their recorder counterparts follow the existing gomock pattern and match the controller signatures, so bulking tests can exercise schema flows without additional changes.

Also applies to: 302-316, 363-376

internal/controller/ledger/controller_with_traces.go (1)

47-49: Histogram wiring for schema operations is consistent

The added insertSchemaHistogram, getSchemaHistogram, and listSchemasHistogram fields are initialized with metric.WithUnit("ms") and follow the same pattern as existing controller latency histograms; no issues here.

Also applies to: 155-166

internal/api/v1/mocks_ledger_controller_test.go (1)

214-227: v1 controller mocks correctly extended for schema operations

The newly added GetSchema, InsertSchema, and ListSchemas mock methods and recorder helpers match the controller interface and follow the existing gomock style, enabling v1 tests to cover schema behavior without further plumbing changes.

Also applies to: 302-316, 363-376

internal/controller/ledger/controller_default_test.go (2)

36-88: Schema-aware CreateTransaction test coverage is well structured

The new testCreateTransaction(t, withSchema bool) helper and TestCreateTransaction wrapper cleanly cover both cases:

  • withSchema == true: pre-insert a schema via the controller, then expect FindSchema("v1.0") and CommitTransaction with the schema.
  • withSchema == false: expect FindLatestSchemaVersion to return nil and CommitTransaction with a nil schema pointer.

The propagation of SchemaVersion into the Parameters[CreateTransaction] request matches the controller’s new schema handling. Once the InsertLog mock return types are adjusted, this provides solid coverage of both branches.

Also applies to: 90-129, 140-147


171-195: FindLatestSchemaVersion expectations align Revert/Metadata flows with schema handling

The added expectations for FindLatestSchemaVersion in TestRevertTransaction, TestSaveTransactionMetadata, and TestDeleteTransactionMetadata ensure these controller paths consult the latest schema when no explicit SchemaVersion is supplied, and then proceed with CommitTransaction using a nil schema pointer. This matches the expected “no explicit schema version” behavior.

Also applies to: 231-233, 281-283

internal/api/v2/mocks_ledger_controller_test.go (1)

214-227: v2 controller mocks correctly extended for schema operations

The GetSchema, InsertSchema, and ListSchemas mock methods and recorder helpers accurately reflect the controller interface and follow the existing gomock patterns, so v2 API tests can set expectations on the new schema endpoints without additional changes.

Also applies to: 302-316, 363-376

internal/controller/ledger/controller_generated_test.go (1)

213-226: LGTM - Generated mocks correctly reflect the new schema controller interface.

The mock signatures for GetSchema, InsertSchema, and ListSchemas correctly match the controller interface definitions, including proper parameter types (Parameters[InsertSchema], common.PaginatedQuery[any]) and return types (*ledger.Schema, *ledger.InsertedSchema, *bunpaginate.Cursor[ledger.Schema]).

Also applies to: 301-315, 362-375

internal/log.go (3)

416-431: LGTM - InsertedSchema type correctly implements LogPayload interface.

The implementation is consistent with other payload types:

  • NeedsSchema() correctly returns false since schema insertion doesn't require validation against an existing schema
  • ValidateWithSchema() returns nil appropriately
  • Interface compliance is verified via var _ LogPayload = (*InsertedSchema)(nil)

218-229: LGTM - Transaction posting validation against schema chart.

The ValidateWithSchema implementation correctly iterates through all postings and validates each against the schema's chart of accounts. Early return on first error is appropriate for fail-fast behavior.


161-183: LGTM - SchemaVersion correctly integrated into log hash computation.

The omitempty tag on SchemaVersion ensures backward compatibility with existing logs that don't have a schema version, maintaining consistent hash computation across versions.

internal/controller/ledger/controller_default.go (4)

57-76: LGTM - Schema insertion with proper error handling.

The implementation correctly:

  • Creates schema using ledger.NewSchema (which may return an error based on the signature)
  • Handles postgres.ErrConstraintsFailed for duplicate version detection
  • Returns the appropriate custom error via newErrSchemaAlreadyExists

278-281: LGTM - Import correctly handles InsertedSchema payloads.

The import path properly persists schemas when replaying logs, maintaining consistency between log history and schema state.


454-454: Note: Unused _schema parameters indicate future validation hooks.

These methods accept a schema parameter but don't currently use it. This aligns with the corresponding ValidateWithSchema implementations in internal/log.go that return nil for these payload types. The design is forward-compatible for adding validation later.

Also applies to: 524-524, 562-562, 584-584


381-448: LGTM - Schema correctly propagated through transaction creation.

The schema is passed from the controller method through to store.CommitTransaction, enabling validation of postings against the chart of accounts at the storage layer.

docs/api/README.md (3)

318-378: Schema endpoint documentation looks correct.

The insert schema endpoint (POST /v2/{ledger}/schema/{version}) is properly documented with:

  • Request body schema (V2SchemaData)
  • Path parameters (ledger, version)
  • Response codes (204 for success, default for error)
  • Required authentication scope (ledger:write)

4550-4555: LGTM - Schema-related error codes properly documented.

The new SCHEMA_ALREADY_EXISTS and SCHEMA_NOT_SPECIFIED error codes are correctly added to the V2ErrorsEnum, aligning with the error handling in the controller layer.


4014-4053: LGTM - V2Log schema correctly updated with schema support.

The schemaVersion field and INSERTED_SCHEMA log type are properly documented, matching the internal Log struct changes in internal/log.go.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6 issues found across 115 files

Prompt for AI agents (all 6 issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="openapi/v2.yaml">

<violation number="1" location="openapi/v2.yaml:2805">
P2: The `V2SchemasCursor` schema is missing the `previous` property that all other cursor types in this API include. This inconsistency will prevent backwards pagination for schema listings, unlike every other paginated endpoint in the API.</violation>
</file>

<file name="internal/log.go">

<violation number="1" location="internal/log.go:25">
P2: Comment says `&quot;INSERT_SCHEMA&quot;` but the actual string value is `&quot;INSERTED_SCHEMA&quot;`. This inconsistency could mislead developers and cause issues if someone uses the comment as reference for API/database values.</violation>
</file>

<file name="pkg/client/docs/models/components/v2chartaccountmetadata.md">

<violation number="1" location="pkg/client/docs/models/components/v2chartaccountmetadata.md:8">
P3: The `Default` field’s type is written as `**string*`, leaving unmatched emphasis markers and rendering incorrectly. Use a balanced emphasis so the type displays clearly (e.g., `*string*`).</violation>
</file>

<file name="internal/api/v2/controllers_schema_list_test.go">

<violation number="1" location="internal/api/v2/controllers_schema_list_test.go:145">
P2: The `expectQuery` field is defined in test cases but never used. The mock uses `gomock.Any()` instead of matching against `tc.expectQuery`, so pagination/sort parameter handling is not actually being verified. Consider using a matcher like `gomock.Eq(tc.expectQuery)` for the second argument.</violation>
</file>

<file name="internal/api/v2/controllers_schema_insert_test.go">

<violation number="1" location="internal/api/v2/controllers_schema_insert_test.go:101">
P2: The &quot;invalid JSON&quot; test case never sends malformed JSON because every request body is marshaled before sending, so regressions in invalid-JSON handling will pass unnoticed.</violation>
</file>

<file name="internal/controller/ledger/controller_default.go">

<violation number="1" location="internal/controller/ledger/controller_default.go:509">
P1: Revert transactions bypass schema validation by calling CommitTransaction with nil even though the schema is provided, so schema rules are not enforced when replaying reversals.</violation>
</file>

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

internal/log.go Outdated
NewTransactionLogType // "NEW_TRANSACTION"
RevertedTransactionLogType // "REVERTED_TRANSACTION"
DeleteMetadataLogType
InsertedSchemaLogType // "INSERT_SCHEMA"
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Comment says "INSERT_SCHEMA" but the actual string value is "INSERTED_SCHEMA". This inconsistency could mislead developers and cause issues if someone uses the comment as reference for API/database values.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/log.go, line 25:

<comment>Comment says `&quot;INSERT_SCHEMA&quot;` but the actual string value is `&quot;INSERTED_SCHEMA&quot;`. This inconsistency could mislead developers and cause issues if someone uses the comment as reference for API/database values.</comment>

<file context>
@@ -22,6 +22,7 @@ const (
 	NewTransactionLogType                     // &quot;NEW_TRANSACTION&quot;
 	RevertedTransactionLogType                // &quot;REVERTED_TRANSACTION&quot;
 	DeleteMetadataLogType
+	InsertedSchemaLogType // &quot;INSERT_SCHEMA&quot;
 )
 
</file context>
Suggested change
InsertedSchemaLogType // "INSERT_SCHEMA"
InsertedSchemaLogType // "INSERTED_SCHEMA"

✅ Addressed in 0cd6e7e

systemController, ledgerController := newTestingSystemController(t, true)
if tc.expectBackendCall {
ledgerController.EXPECT().
ListSchemas(gomock.Any(), gomock.Any()).
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The expectQuery field is defined in test cases but never used. The mock uses gomock.Any() instead of matching against tc.expectQuery, so pagination/sort parameter handling is not actually being verified. Consider using a matcher like gomock.Eq(tc.expectQuery) for the second argument.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/api/v2/controllers_schema_list_test.go, line 145:

<comment>The `expectQuery` field is defined in test cases but never used. The mock uses `gomock.Any()` instead of matching against `tc.expectQuery`, so pagination/sort parameter handling is not actually being verified. Consider using a matcher like `gomock.Eq(tc.expectQuery)` for the second argument.</comment>

<file context>
@@ -0,0 +1,170 @@
+			systemController, ledgerController := newTestingSystemController(t, true)
+			if tc.expectBackendCall {
+				ledgerController.EXPECT().
+					ListSchemas(gomock.Any(), gomock.Any()).
+					Return(tc.returnCursor, tc.returnErr)
+			}
</file context>
Suggested change
ListSchemas(gomock.Any(), gomock.Any()).
ListSchemas(gomock.Any(), tc.expectQuery).

✅ Addressed in 8ac494d

}

err = store.CommitTransaction(ctx, &reversedTx, nil)
err = store.CommitTransaction(ctx, nil, &reversedTx, nil)
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Revert transactions bypass schema validation by calling CommitTransaction with nil even though the schema is provided, so schema rules are not enforced when replaying reversals.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/controller/ledger/controller_default.go, line 509:

<comment>Revert transactions bypass schema validation by calling CommitTransaction with nil even though the schema is provided, so schema rules are not enforced when replaying reversals.</comment>

<file context>
@@ -471,7 +506,7 @@ func (ctrl *DefaultController) revertTransaction(ctx context.Context, store Stor
 	}
 
-	err = store.CommitTransaction(ctx, &amp;reversedTx, nil)
+	err = store.CommitTransaction(ctx, nil, &amp;reversedTx, nil)
 	if err != nil {
 		return nil, fmt.Errorf(&quot;failed to insert transaction: %w&quot;, err)
</file context>
Suggested change
err = store.CommitTransaction(ctx, nil, &reversedTx, nil)
err = store.CommitTransaction(ctx, _schema, &reversedTx, nil)
Fix with Cubic

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (4)
internal/log.go (2)

25-25: Fix comment to match actual serialization.

The comment shows "INSERT_SCHEMA" but the actual string value (line 65) is "INSERTED_SCHEMA".

-	InsertedSchemaLogType                     // "INSERT_SCHEMA"
+	InsertedSchemaLogType                     // "INSERTED_SCHEMA"

449-449: Critical: Fix HydrateLog unmarshalling.

Passing &payload to json.Unmarshal causes the concrete struct pointers to be replaced with generic types. This should be json.Unmarshal(data, payload) to unmarshal into the existing concrete pointer.

-	err := json.Unmarshal(data, &payload)
+	err := json.Unmarshal(data, payload)
internal/README.md (1)

1190-1190: Documentation reflects source code issue.

This auto-generated documentation shows "INSERT_SCHEMA" because the source code comment in internal/log.go line 25 is incorrect. Once that source comment is fixed to "INSERTED_SCHEMA", regenerating this documentation will automatically fix it.

internal/api/v2/controllers_schema_insert_test.go (1)

58-64: Test case doesn't validate invalid JSON handling.

The test named "invalid body" marshals a string value, producing valid JSON ("not an object"), so it doesn't test malformed JSON handling.

To test actual invalid JSON parsing, use raw bytes:

 		{
-			name:              "invalid body",
+			name:              "invalid body",
 			version:           "v1.0.0",
-			requestBody:       "not an object",
 			expectStatusCode:  http.StatusBadRequest,
 			expectedErrorCode: "VALIDATION",
 			expectBackendCall: false,
 		},

And update the test execution (around line 101):

// For the invalid JSON test case, use raw malformed JSON
var body []byte
if tc.name == "invalid body" {
	body = []byte("{invalid json")
} else {
	body, _ = json.Marshal(tc.requestBody)
}
🧹 Nitpick comments (1)
internal/log.go (1)

272-277: Consider whether SavedMetadata truly needs schema.

SavedMetadata.NeedsSchema() returns true but ValidateWithSchema() performs no validation (returns nil). If metadata operations don't require schema validation, consider returning false from NeedsSchema() for consistency.

The same pattern appears in DeletedMetadata (lines 324-329) and RevertedTransaction (lines 375-380).

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 18de8a9 and fb3e5ed.

⛔ Files ignored due to path filters (5)
  • openapi.yaml is excluded by !**/*.yaml
  • openapi/v2.yaml is excluded by !**/*.yaml
  • pkg/client/.speakeasy/gen.lock is excluded by !**/*.lock, !**/*.lock
  • pkg/client/.speakeasy/logs/naming.log is excluded by !**/*.log, !**/*.log
  • pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log is excluded by !**/*.log, !**/*.log
📒 Files selected for processing (14)
  • docs/api/README.md (25 hunks)
  • internal/README.md (51 hunks)
  • internal/api/v2/controllers_schema_insert_test.go (1 hunks)
  • internal/api/v2/controllers_schema_list_test.go (1 hunks)
  • internal/log.go (13 hunks)
  • pkg/client/docs/models/components/v2accountscursorresponsecursor.md (1 hunks)
  • pkg/client/docs/models/components/v2ledgerlistresponsecursor.md (1 hunks)
  • pkg/client/docs/models/components/v2listexportersresponsecursorcursor.md (1 hunks)
  • pkg/client/docs/models/components/v2listpipelinesresponsecursorcursor.md (1 hunks)
  • pkg/client/docs/models/components/v2logscursorresponsecursor.md (1 hunks)
  • pkg/client/docs/models/components/v2schemascursor.md (1 hunks)
  • pkg/client/docs/models/components/v2transactionscursorresponsecursor.md (1 hunks)
  • pkg/client/docs/models/components/v2volumeswithbalancecursorresponsecursor.md (1 hunks)
  • pkg/client/models/components/v2schemascursor.go (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • pkg/client/docs/models/components/v2volumeswithbalancecursorresponsecursor.md
  • pkg/client/docs/models/components/v2listexportersresponsecursorcursor.md
🚧 Files skipped from review as they are similar to previous changes (2)
  • pkg/client/models/components/v2schemascursor.go
  • pkg/client/docs/models/components/v2schemascursor.md
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-04-29T11:24:28.923Z
Learnt from: gfyrag
Repo: formancehq/ledger PR: 892
File: internal/controller/ledger/controller_default.go:196-196
Timestamp: 2025-04-29T11:24:28.923Z
Learning: In the ledger Import function, it's critical to maintain proper log ID tracking by updating lastLogID with the current log.ID after each processed log, rather than setting it to nil. This ensures the system can properly validate the ordering of logs and prevent duplicate or out-of-order processing, which is essential for maintaining data integrity in the ledger.

Applied to files:

  • internal/README.md
🧬 Code graph analysis (3)
internal/api/v2/controllers_schema_list_test.go (2)
internal/storage/common/pagination.go (3)
  • PaginatedQuery (26-29)
  • InitialPaginatedQuery (10-15)
  • OffsetPaginatedQuery (16-19)
internal/schema.go (2)
  • Schema (15-21)
  • SchemaData (11-13)
internal/api/v2/controllers_schema_insert_test.go (2)
internal/controller/ledger/errors.go (1)
  • ErrSchemaValidationError (267-270)
internal/controller/ledger/controller.go (1)
  • InsertSchema (135-138)
internal/log.go (4)
internal/schema.go (1)
  • Schema (15-21)
internal/transaction.go (2)
  • Transaction (37-51)
  • Transaction (53-60)
internal/posting.go (1)
  • Postings (35-35)
pkg/events/message.go (1)
  • InsertedSchema (77-80)
🪛 markdownlint-cli2 (0.18.1)
docs/api/README.md

357-357: Blank line inside blockquote

(MD028, no-blanks-blockquote)


403-403: Blank line inside blockquote

(MD028, no-blanks-blockquote)


461-461: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4

(MD001, heading-increment)


470-470: Blank line inside blockquote

(MD028, no-blanks-blockquote)


5616-5616: Multiple headings with the same content

(MD024, no-duplicate-heading)


5618-5618: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


5634-5634: Multiple headings with the same content

(MD024, no-duplicate-heading)


5660-5660: Multiple headings with the same content

(MD024, no-duplicate-heading)


5691-5691: Multiple headings with the same content

(MD024, no-duplicate-heading)


5719-5719: Multiple headings with the same content

(MD024, no-duplicate-heading)


5751-5751: Multiple headings with the same content

(MD024, no-duplicate-heading)


5785-5785: Multiple headings with the same content

(MD024, no-duplicate-heading)


5825-5825: Multiple headings with the same content

(MD024, no-duplicate-heading)


5863-5863: Multiple headings with the same content

(MD024, no-duplicate-heading)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Tests
  • GitHub Check: GoReleaser
  • GitHub Check: Dirty
🔇 Additional comments (16)
pkg/client/docs/models/components/v2accountscursorresponsecursor.md (1)

11-11: Verify this documentation file was regenerated via proper tooling.

The example value for the Next field has been updated to a realistic non-empty Base64-encoded string, which aligns with broader cursor documentation updates across the PR. However, this file appears to be auto-generated client documentation, which typically should not be manually edited.

Please confirm whether this file was regenerated using the appropriate code generation tooling (e.g., OpenAPI generator, Swagger codegen, etc.) rather than manually edited. If auto-generated, ensure the proper regeneration workflow was followed to keep the generated files in sync with the source definitions.

pkg/client/docs/models/components/v2transactionscursorresponsecursor.md (1)

11-11: LGTM! Documentation example improved.

The non-empty base64 example for the Next field provides better documentation for pagination token usage.

pkg/client/docs/models/components/v2listpipelinesresponsecursorcursor.md (1)

11-11: LGTM! Consistent documentation improvement.

The non-empty example aligns with the pagination token pattern used across other cursor documentation.

pkg/client/docs/models/components/v2logscursorresponsecursor.md (1)

11-11: LGTM! Documentation consistency maintained.

pkg/client/docs/models/components/v2ledgerlistresponsecursor.md (1)

11-11: LGTM! Documentation pattern consistent.

internal/api/v2/controllers_schema_list_test.go (1)

142-146: LGTM! Past review concern addressed.

The mock now correctly validates the query parameter using tc.expectQuery instead of gomock.Any(), ensuring pagination and sort parameters are properly verified.

internal/log.go (3)

101-101: LGTM! SchemaVersion field properly integrated.

The SchemaVersion field is correctly added to the Log struct with appropriate JSON and Bun tags, and is properly included in hash computation (lines 169, 177).


190-192: LGTM! Clean delegation pattern.

The ValidateWithSchema method correctly delegates to the payload's validation logic.


416-431: LGTM! InsertedSchema implementation is correct.

The InsertedSchema payload correctly returns false for NeedsSchema() since schema insertion doesn't require validation against an existing schema.

internal/README.md (1)

19-257: LGTM! Comprehensive schema API documentation.

The auto-generated documentation properly captures the new schema-related public API surface, including ChartOfAccounts, ChartSegment, Schema, and related types and methods.

internal/api/v2/controllers_schema_insert_test.go (1)

21-116: Test structure is well-designed.

The table-driven test properly covers nominal cases, empty data, backend errors, and validation errors. Good use of parallel execution and proper assertions.

docs/api/README.md (5)

318-509: Schema endpoints are correctly defined and documented. The three operations (insert, get, list) follow REST conventions with appropriate HTTP methods, parameters, and response types. The wording on line 318 correctly reflects the insert-only semantics (addressing prior feedback). Pagination support for the list endpoint is good practice.


734-734: schemaVersion parameters correctly integrated across affected endpoints. All write operations (create transaction, metadata operations, revert) consistently expose an optional query parameter for schema version. Descriptions are clear, and backward compatibility is maintained by making the parameter optional. Bulk request endpoint (line 734) properly notes that the parameter can be overridden per element.

Also applies to: 1139-1139, 1566-1566, 1842-1842, 1907-1907


2219-2220: schemaVersion field correctly added to V2Log. The field is optional, which maintains backward compatibility. The description "Schema version used for validation" is clear. The new log type INSERTED_SCHEMA (line 4053) is properly integrated into the enumerated values, supporting schema creation events.

Also applies to: 4028-4043, 4053-4053


4553-4554: New error codes added appropriately. SCHEMA_ALREADY_EXISTS and SCHEMA_NOT_SPECIFIED align with the insert-only schema semantics and validation requirements. Error code naming is clear and self-documenting.


5604-5872: V2ChartSegment documentation could be enriched, but no linting violations or CI issues are present.

The schema models are structurally sound. While V2ChartSegment uses a recursive, self-referential structure with fields like .pattern, .rules, .metadata, and .self, the documentation for these fields is minimal (descriptions show "none"). For improved clarity, consider enriching the OpenAPI specification to describe:

  • The purpose of .pattern (e.g., validation regex for account identifiers)
  • When .rules and .metadata apply vs. remain unused
  • A realistic example showing how nested segments map to account hierarchies

The file is auto-generated by Widdershins v4.0.1 from the OpenAPI spec. There are no active linting checks in the CI pipeline that would block this PR based on markdown style violations.

Likely an incorrect or invalid review comment.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 122 files

Prompt for AI agents (all 2 issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="internal/controller/ledger/controller_with_traces.go">

<violation number="1" location="internal/controller/ledger/controller_with_traces.go:482">
P2: Inconsistent span name: should be `&quot;InsertSchema&quot;` to match the method name, not `&quot;InsertedSchema&quot;`. All other methods in this file use the exact method name as the span name (e.g., `CreateTransaction`, `GetSchema`, `ListSchemas`).</violation>
</file>

<file name="internal/api/v2/controllers_schema_list_test.go">

<violation number="1" location="internal/api/v2/controllers_schema_list_test.go:83">
P2: Inconsistent `Options` field: this test case doesn&#39;t set `Expand: make([]string, 0)` like the other test cases do. If the actual implementation always initializes `Expand` to an empty slice, gomock&#39;s comparison will fail because `nil != []string{}`. Consider adding `Expand: make([]string, 0)` for consistency.</violation>
</file>

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

expectQuery: storagecommon.OffsetPaginatedQuery[any]{
InitialPaginatedQuery: storagecommon.InitialPaginatedQuery[any]{
PageSize: 10,
Options: storagecommon.ResourceQuery[any]{},
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Inconsistent Options field: this test case doesn't set Expand: make([]string, 0) like the other test cases do. If the actual implementation always initializes Expand to an empty slice, gomock's comparison will fail because nil != []string{}. Consider adding Expand: make([]string, 0) for consistency.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/api/v2/controllers_schema_list_test.go, line 83:

<comment>Inconsistent `Options` field: this test case doesn&#39;t set `Expand: make([]string, 0)` like the other test cases do. If the actual implementation always initializes `Expand` to an empty slice, gomock&#39;s comparison will fail because `nil != []string{}`. Consider adding `Expand: make([]string, 0)` for consistency.</comment>

<file context>
@@ -0,0 +1,169 @@
+			expectQuery: storagecommon.OffsetPaginatedQuery[any]{
+				InitialPaginatedQuery: storagecommon.InitialPaginatedQuery[any]{
+					PageSize: 10,
+					Options:  storagecommon.ResourceQuery[any]{},
+				},
+				Offset: 0,
</file context>
Suggested change
Options: storagecommon.ResourceQuery[any]{},
Options: storagecommon.ResourceQuery[any]{
Expand: make([]string, 0),
},
Fix with Cubic

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
docs/api/README.md (1)

5636-5665: Chart segment structure is correct but could benefit from clearer documentation on nesting semantics.

The V2ChartSegment recursive structure is appropriate for hierarchical account definitions, but the documentation lacks clarity on intended usage:

Current state:

  • Recursive additionalProperties pointing to V2ChartSegment allows nesting
  • Optional .pattern, .rules, .metadata properties are defined but lack explanation of their interplay
  • Example shows basic structure (users: { $userID: { .pattern: ... } }) but doesn't clarify how intermediate vs. leaf segments differ

Recommendation: Add a brief note (inline or in the description at line 5654) explaining:

  • When to use .pattern (validation regex for this segment)
  • When to use .rules (custom validation rules for this node)
  • When to use .metadata (node-specific attributes)
  • How recursive nesting works (intermediate segments vs. leaves)

Example: "Chart segments are hierarchical. Use .pattern for segment validation, .rules for custom constraints, .metadata for segment-level attributes, and nest segments recursively to build complex account structures."

This aligns with the prior review's suggestion and would prevent client confusion when implementing schema-based account hierarchies.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between fb3e5ed and 388ed81.

⛔ Files ignored due to path filters (5)
  • openapi.yaml is excluded by !**/*.yaml
  • openapi/v2.yaml is excluded by !**/*.yaml
  • pkg/client/.speakeasy/gen.lock is excluded by !**/*.lock, !**/*.lock
  • pkg/client/.speakeasy/logs/naming.log is excluded by !**/*.log, !**/*.log
  • pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log is excluded by !**/*.log, !**/*.log
📒 Files selected for processing (3)
  • docs/api/README.md (25 hunks)
  • pkg/client/docs/models/components/v2schema.md (1 hunks)
  • pkg/client/models/components/v2schema.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • pkg/client/docs/models/components/v2schema.md
🧰 Additional context used
🪛 LanguageTool
docs/api/README.md

[grammar] ~5746-~5746: Ensure spelling is correct
Context: ...tructure with metadata ### Properties allOf |Name|Type|Required|Restrictions|Descrip...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🪛 markdownlint-cli2 (0.18.1)
docs/api/README.md

357-357: Blank line inside blockquote

(MD028, no-blanks-blockquote)


403-403: Blank line inside blockquote

(MD028, no-blanks-blockquote)


459-459: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4

(MD001, heading-increment)


468-468: Blank line inside blockquote

(MD028, no-blanks-blockquote)


5612-5612: Multiple headings with the same content

(MD024, no-duplicate-heading)


5614-5614: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


5630-5630: Multiple headings with the same content

(MD024, no-duplicate-heading)


5656-5656: Multiple headings with the same content

(MD024, no-duplicate-heading)


5687-5687: Multiple headings with the same content

(MD024, no-duplicate-heading)


5715-5715: Multiple headings with the same content

(MD024, no-duplicate-heading)


5745-5745: Multiple headings with the same content

(MD024, no-duplicate-heading)


5785-5785: Multiple headings with the same content

(MD024, no-duplicate-heading)


5823-5823: Multiple headings with the same content

(MD024, no-duplicate-heading)


5859-5859: Multiple headings with the same content

(MD024, no-duplicate-heading)

🔇 Additional comments (6)
pkg/client/models/components/v2schema.go (1)

1-50: Generated code structure looks correct; UnmarshalJSON concern previously acknowledged.

This Speakeasy-generated file implements the V2Schema model with proper JSON marshaling, nil-safe accessors, and documented fields. As noted in previous reviews, the UnmarshalJSON method at lines 24-27 passes &v (creating **V2Schema) to the utils helper, which you've already explained is part of the generator's implementation and cannot be manually modified.

The overall structure is appropriate for generated code, and no new concerns are identified beyond what was previously discussed.

docs/api/README.md (5)

318-506: Schema endpoints are well-documented and semantically correct.

The three new schema operations (insert, get, list) are properly structured with:

  • Clear semantics: POST is insert-only (204 No Content response, no body), GET operations return 200 with data
  • Correct parameter descriptions and example payloads
  • Proper error responses linked to V2ErrorResponse
  • Consistent with the prior review feedback (title correctly states "Insert a schema" rather than "Insert or update")

No issues flagged.


730-730: schemaVersion parameter additions are consistent and well-integrated.

The optional schemaVersion query parameter is added consistently across bulk, transaction, and metadata endpoints with appropriate descriptions:

  • Bulk endpoint (line 730): "Default schema version to use for validation (can be overridden per element)" — correctly notes override semantics
  • Transaction & metadata endpoints (lines 1562, 1838, 1903, 1135): "Schema version to use for validation" — simple and clear

All are properly marked as optional strings, supporting backward compatibility. Descriptions vary slightly but appropriately reflect endpoint-specific semantics.

Also applies to: 1135-1135, 1562-1562, 1838-1838, 1903-1903


4024-4039: V2Log schema updates properly integrate schemaVersion.

The schemaVersion field is correctly added to the V2Log schema:

  • Marked as optional (false in Required column, line 4039) — appropriate for backward compatibility
  • Documentation: "Schema version used for validation" — clear and accurate
  • Example payload includes field (line 2216: "schemaVersion": "v1.0.0")
  • New INSERTED_SCHEMA log type added to enumeration (line 4049) — semantically sound

No issues flagged.

Also applies to: 4041-4049


4549-4550: New error codes are semantically correct and properly placed.

The two new error types are appropriate and well-positioned:

  • SCHEMA_ALREADY_EXISTS: Aligns with insert-only schema endpoint semantics (conflicts on duplicate version)
  • SCHEMA_NOT_SPECIFIED: Supports validation-required operations that lack schema context

Both fit the existing error enumeration pattern and are referenced by the documented endpoints.


5600-5868: Markdownlint violations in schema sections exist but do not block CI.

The file contains real violations:

  • MD024 (duplicate "### Properties" headings): Multiple instances at lines 5687, 5715, 5745, 5785, 5823, 5859, and throughout
  • MD036 (emphasis as heading): "None" at line 5614
  • MD028 (blank lines in blockquotes): Present at lines 357, 403, 468

However, docs/api/README.md is auto-generated by Widdershins (header confirms "") and is regenerated during the openapi Justfile target. The repository's CI pipeline (Justfile) runs only golangci-lint for linting—there is no markdownlint invocation. These violations do not prevent merge.

If the violations should be addressed:

  • Option A: Update the Widdershins template or post-processing to avoid duplicate headings and format *None* properly
  • Option B: Add markdownlint configuration and a CI target to validate the generated markdown
  • Option C: Accept them as a known limitation of the auto-generated output (no action needed)

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 issues found across 122 files

Note: This PR contains a large number of files. cubic only reviews up to 75 files per PR, so some files may not have been reviewed.

Prompt for AI agents (all 5 issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="internal/controller/ledger/controller_with_traces.go">

<violation number="1" location="internal/controller/ledger/controller_with_traces.go:482">
P2: Trace name &quot;InsertedSchema&quot; is inconsistent with the method name `InsertSchema`. All other methods use their exact method name for tracing (e.g., &quot;CreateTransaction&quot;, &quot;GetSchema&quot;, &quot;ListSchemas&quot;). This inconsistency could make trace analysis and filtering more difficult.</violation>
</file>

<file name="internal/api/common/errors.go">

<violation number="1" location="internal/api/common/errors.go:43">
P1: `errors.Unwrap(err)` will return `nil` because `ErrSchemaNotSpecified{}` doesn&#39;t implement the `Unwrap()` method. Use `err` directly to return the actual error message, consistent with existing patterns (e.g., line 58).</violation>

<violation number="2" location="internal/api/common/errors.go:45">
P1: `errors.Unwrap(err)` will return `nil` because `ErrSchemaNotFound{}` doesn&#39;t implement the `Unwrap()` method. Use `err` directly to return the actual error message.</violation>

<violation number="3" location="internal/api/common/errors.go:60">
P1: `errors.Unwrap(err)` will return `nil` because `ErrSchemaValidationError{}` doesn&#39;t implement the `Unwrap()` method. Use `err` directly to return the actual error message.</violation>
</file>

<file name="internal/errors.go">

<violation number="1" location="internal/errors.go:118">
P3: Typo: &quot;chart of account&quot; should be &quot;chart of accounts&quot; to be consistent with the other error messages in this function.</violation>
</file>

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

case errors.Is(err, ledgercontroller.ErrNotFound):
api.NotFound(w, err)
case errors.Is(err, ledgercontroller.ErrSchemaValidationError{}):
api.BadRequest(w, ErrValidation, errors.Unwrap(err))
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: errors.Unwrap(err) will return nil because ErrSchemaValidationError{} doesn't implement the Unwrap() method. Use err directly to return the actual error message.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/api/common/errors.go, line 60:

<comment>`errors.Unwrap(err)` will return `nil` because `ErrSchemaValidationError{}` doesn&#39;t implement the `Unwrap()` method. Use `err` directly to return the actual error message.</comment>

<file context>
@@ -50,6 +56,12 @@ func HandleCommonWriteErrors(w http.ResponseWriter, r *http.Request, err error)
 	case errors.Is(err, ledgercontroller.ErrNotFound):
 		api.NotFound(w, err)
+	case errors.Is(err, ledgercontroller.ErrSchemaValidationError{}):
+		api.BadRequest(w, ErrValidation, errors.Unwrap(err))
+	case errors.Is(err, ledgercontroller.ErrSchemaNotSpecified{}):
+		api.BadRequest(w, ErrSchemaNotSpecified, errors.Unwrap(err))
</file context>
Suggested change
api.BadRequest(w, ErrValidation, errors.Unwrap(err))
api.BadRequest(w, ErrValidation, err)
Fix with Cubic

case errors.Is(err, ledgercontroller.ErrSchemaNotSpecified{}):
api.BadRequest(w, ErrSchemaNotSpecified, errors.Unwrap(err))
case errors.Is(err, ledgercontroller.ErrSchemaNotFound{}):
api.NotFound(w, errors.Unwrap(err))
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: errors.Unwrap(err) will return nil because ErrSchemaNotFound{} doesn't implement the Unwrap() method. Use err directly to return the actual error message.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/api/common/errors.go, line 45:

<comment>`errors.Unwrap(err)` will return `nil` because `ErrSchemaNotFound{}` doesn&#39;t implement the `Unwrap()` method. Use `err` directly to return the actual error message.</comment>

<file context>
@@ -37,6 +39,10 @@ func HandleCommonErrors(w http.ResponseWriter, r *http.Request, err error) {
+	case errors.Is(err, ledgercontroller.ErrSchemaNotSpecified{}):
+		api.BadRequest(w, ErrSchemaNotSpecified, errors.Unwrap(err))
+	case errors.Is(err, ledgercontroller.ErrSchemaNotFound{}):
+		api.NotFound(w, errors.Unwrap(err))
 	default:
 		InternalServerError(w, r, err)
</file context>
Suggested change
api.NotFound(w, errors.Unwrap(err))
api.NotFound(w, err)
Fix with Cubic

case errors.Is(err, postgres.ErrTooManyClient{}):
api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err)
case errors.Is(err, ledgercontroller.ErrSchemaNotSpecified{}):
api.BadRequest(w, ErrSchemaNotSpecified, errors.Unwrap(err))
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: errors.Unwrap(err) will return nil because ErrSchemaNotSpecified{} doesn't implement the Unwrap() method. Use err directly to return the actual error message, consistent with existing patterns (e.g., line 58).

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/api/common/errors.go, line 43:

<comment>`errors.Unwrap(err)` will return `nil` because `ErrSchemaNotSpecified{}` doesn&#39;t implement the `Unwrap()` method. Use `err` directly to return the actual error message, consistent with existing patterns (e.g., line 58).</comment>

<file context>
@@ -37,6 +39,10 @@ func HandleCommonErrors(w http.ResponseWriter, r *http.Request, err error) {
 	case errors.Is(err, postgres.ErrTooManyClient{}):
 		api.WriteErrorResponse(w, http.StatusServiceUnavailable, api.ErrorInternal, err)
+	case errors.Is(err, ledgercontroller.ErrSchemaNotSpecified{}):
+		api.BadRequest(w, ErrSchemaNotSpecified, errors.Unwrap(err))
+	case errors.Is(err, ledgercontroller.ErrSchemaNotFound{}):
+		api.NotFound(w, errors.Unwrap(err))
</file context>
Suggested change
api.BadRequest(w, ErrSchemaNotSpecified, errors.Unwrap(err))
api.BadRequest(w, ErrSchemaNotSpecified, err)
Fix with Cubic

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 12 files (changes from recent commits).

Prompt for AI agents (all 1 issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="internal/chart.go">

<violation number="1" location="internal/chart.go:203">
P1: This condition will always be `false` because `ChartAccountRules` is an empty struct (`type ChartAccountRules struct{}`). In Go, all instances of an empty struct are equal, so comparing against `(ChartAccountRules{})` will never yield a difference. As a result, `RULES_KEY` and `SELF_KEY` will never be serialized. If `ChartAccountRules` is intended to have fields added later, consider removing this condition until fields exist, or add fields to make the comparison meaningful.</violation>
</file>

Reply to cubic to teach it or ask questions. Re-run a review with @cubic-dev-ai review this PR

if s.Account.Metadata != nil {
out[METADATA_KEY] = s.Account.Metadata
}
if s.Account.Rules != (ChartAccountRules{}) {
Copy link

@cubic-dev-ai cubic-dev-ai bot Dec 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: This condition will always be false because ChartAccountRules is an empty struct (type ChartAccountRules struct{}). In Go, all instances of an empty struct are equal, so comparing against (ChartAccountRules{}) will never yield a difference. As a result, RULES_KEY and SELF_KEY will never be serialized. If ChartAccountRules is intended to have fields added later, consider removing this condition until fields exist, or add fields to make the comparison meaningful.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At internal/chart.go, line 203:

<comment>This condition will always be `false` because `ChartAccountRules` is an empty struct (`type ChartAccountRules struct{}`). In Go, all instances of an empty struct are equal, so comparing against `(ChartAccountRules{})` will never yield a difference. As a result, `RULES_KEY` and `SELF_KEY` will never be serialized. If `ChartAccountRules` is intended to have fields added later, consider removing this condition until fields exist, or add fields to make the comparison meaningful.</comment>

<file context>
@@ -200,9 +200,11 @@ func (s ChartSegment) marshalJsonObject() (map[string]any, error) {
-		out[RULES_KEY] = s.Account.Rules
-		if len(s.FixedSegments) &gt; 0 || s.VariableSegment != nil {
-			out[SELF_KEY] = map[string]interface{}{}
+		if s.Account.Rules != (ChartAccountRules{}) {
+			out[RULES_KEY] = s.Account.Rules
+			if len(s.FixedSegments) &gt; 0 || s.VariableSegment != nil {
</file context>

✅ Addressed in c424d5a

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
internal/README.md (1)

253-257: Reconfirm regex consistency between chart segments and account addresses (already flagged before)

ChartSegmentRegexp is documented as ^(\$|\.)?[a-zA-Z0-9_-]+$, which allows - in segment names. The HTTP API’s account-address regex currently only allows \w (no -). That means you can define chart segments that can never be referenced by valid addresses.

If this divergence isn’t intentional, it’d be good to align the two (either tighten ChartSegmentRegexp or broaden the address regex in the HTTP surface) and then regenerate this README. This repeats an existing comment, but the inconsistency still appears here.

🧹 Nitpick comments (2)
internal/chart_test.go (1)

391-415: Name the failing cases in TestPostingValidation for clearer diagnostics

Two table entries in TestPostingValidation have name: "", so failures will show up without a helpful case name. Consider giving them descriptive names (e.g. "invalid source account", "invalid destination account") so failures are easier to interpret.

internal/chart.go (1)

247-271: Avoid recompiling regex on every account lookup

findAccountSchema calls regexp.Match(variableSegment.Pattern, ...) on every lookup, which recompiles the pattern each time even though it was already validated during unmarshal.

This is not a correctness bug, but it’s unnecessary overhead on hot paths (especially if schemas are large and lookups frequent).

Consider precompiling once and storing the compiled regexp on the variable segment:

 type ChartVariableSegment struct {
-    ChartSegment
-
-    Pattern string
-    Label   string
+    ChartSegment
+
+    Pattern string
+    Label   string
+
+    re *regexp.Regexp // compiled pattern, not exported/serialized
 }
@@ in (*ChartSegment).UnmarshalJSON when setting variableSegment:
-                variableSegment = &ChartVariableSegment{
-                    ChartSegment: segment,
-                    Pattern:      *pattern,
-                    Label:        key[1:],
-                }
+                compiled, _ := regexp.Compile(*pattern) // safe: already validated above
+                variableSegment = &ChartVariableSegment{
+                    ChartSegment: segment,
+                    Pattern:      *pattern,
+                    Label:        key[1:],
+                    re:           compiled,
+                }
@@ in findAccountSchema:
-    if variableSegment != nil {
-        matches, err := regexp.Match(variableSegment.Pattern, []byte(nextSegment))
-        if err != nil {
-            return nil, fmt.Errorf("invalid pattern regex: %v", err)
-        }
-        if matches {
+    if variableSegment != nil {
+        if variableSegment.re.MatchString(nextSegment) {

You can then drop the runtime invalid pattern regex branch here, since patterns are already validated when building the chart.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 388ed81 and b75d5f1.

⛔ Files ignored due to path filters (3)
  • openapi.yaml is excluded by !**/*.yaml
  • openapi/v2.yaml is excluded by !**/*.yaml
  • pkg/client/.speakeasy/gen.lock is excluded by !**/*.lock, !**/*.lock
📒 Files selected for processing (9)
  • docs/api/README.md (25 hunks)
  • internal/README.md (51 hunks)
  • internal/chart.go (1 hunks)
  • internal/chart_test.go (1 hunks)
  • internal/controller/ledger/controller.go (2 hunks)
  • internal/controller/ledger/controller_with_traces.go (3 hunks)
  • internal/errors.go (2 hunks)
  • pkg/client/docs/models/components/v2chartsegment.md (1 hunks)
  • pkg/client/models/components/v2chartsegment.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • pkg/client/models/components/v2chartsegment.go
  • internal/controller/ledger/controller.go
  • internal/controller/ledger/controller_with_traces.go
  • pkg/client/docs/models/components/v2chartsegment.md
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-04-29T11:24:28.923Z
Learnt from: gfyrag
Repo: formancehq/ledger PR: 892
File: internal/controller/ledger/controller_default.go:196-196
Timestamp: 2025-04-29T11:24:28.923Z
Learning: In the ledger Import function, it's critical to maintain proper log ID tracking by updating lastLogID with the current log.ID after each processed log, rather than setting it to nil. This ensures the system can properly validate the ordering of logs and prevent duplicate or out-of-order processing, which is essential for maintaining data integrity in the ledger.

Applied to files:

  • internal/README.md
🧬 Code graph analysis (2)
internal/chart_test.go (2)
internal/chart.go (6)
  • ChartOfAccounts (40-40)
  • ChartVariableSegment (27-32)
  • ChartSegment (21-25)
  • ChartAccount (16-19)
  • ChartAccountRules (10-10)
  • ChartAccountMetadata (12-14)
internal/errors.go (1)
  • ErrInvalidAccount (103-108)
internal/chart.go (2)
internal/storage/ledger/logs.go (1)
  • RawMessage (38-38)
internal/errors.go (1)
  • ErrInvalidAccount (103-108)
🪛 LanguageTool
docs/api/README.md

[grammar] ~5746-~5746: Ensure spelling is correct
Context: ...tructure with metadata ### Properties allOf |Name|Type|Required|Restrictions|Descrip...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🪛 markdownlint-cli2 (0.18.1)
docs/api/README.md

357-357: Blank line inside blockquote

(MD028, no-blanks-blockquote)


403-403: Blank line inside blockquote

(MD028, no-blanks-blockquote)


459-459: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4

(MD001, heading-increment)


468-468: Blank line inside blockquote

(MD028, no-blanks-blockquote)


5612-5612: Multiple headings with the same content

(MD024, no-duplicate-heading)


5614-5614: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


5630-5630: Multiple headings with the same content

(MD024, no-duplicate-heading)


5656-5656: Multiple headings with the same content

(MD024, no-duplicate-heading)


5687-5687: Multiple headings with the same content

(MD024, no-duplicate-heading)


5715-5715: Multiple headings with the same content

(MD024, no-duplicate-heading)


5745-5745: Multiple headings with the same content

(MD024, no-duplicate-heading)


5785-5785: Multiple headings with the same content

(MD024, no-duplicate-heading)


5823-5823: Multiple headings with the same content

(MD024, no-duplicate-heading)


5859-5859: Multiple headings with the same content

(MD024, no-duplicate-heading)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: Tests
  • GitHub Check: GoReleaser
  • GitHub Check: Dirty
🔇 Additional comments (6)
docs/api/README.md (5)

318-505: ✓ Schema endpoints well-documented.

The three new schema management endpoints (insert, get, list) are properly documented with clear parameter descriptions, correct HTTP methods, and realistic examples. The endpoint descriptions accurately reflect insert-only semantics (no update).


730-730: ✓ SchemaVersion parameters consistently integrated.

The schemaVersion query parameter is properly added across all relevant endpoints (bulk, add metadata, create/revert transaction) with appropriate optional status and clear descriptions. The bulk endpoint notably specifies element-level override capability.

Also applies to: 1135-1135, 1562-1562, 1838-1838, 1903-1903


4018-4050: ✓ Log schema and error codes properly extended.

The V2Log schema correctly adds an optional schemaVersion field with clear documentation. New log type INSERTED_SCHEMA is properly enumerated. Error codes SCHEMA_ALREADY_EXISTS and SCHEMA_NOT_SPECIFIED are appropriately defined in V2ErrorsEnum for schema validation scenarios.

Also applies to: 4549-4551


5600-5868: ✓ Schema types comprehensive and well-structured.

New schema types (V2ChartOfAccounts, V2ChartSegment, V2SchemaData, V2Schema, V2SchemaResponse, pagination cursors) are properly defined with appropriate nesting, recursive structures, and descriptions. Type composition using allOf follows OpenAPI standards correctly.

Note: Auto-generated schema section retains markdownlint issues (MD024 duplicate headings, MD036 emphasis-as-heading) previously flagged. These may require generator-level fixes or CI configuration adjustments to suppress in auto-generated sections.


1-6250: ✓ Schema feature documentation is comprehensive and well-integrated.

The documentation comprehensively mirrors the backend schema feature implementation:

  • Three schema management endpoints (POST insert, GET read, GET list)
  • SchemaVersion parameter propagation across transaction, metadata, and bulk operations
  • V2Log schema extended with schemaVersion field and new INSERTED_SCHEMA event type
  • Complete type definitions for chart-of-accounts structures and schema responses
  • Error code documentation for schema-specific failures (SCHEMA_ALREADY_EXISTS, SCHEMA_NOT_SPECIFIED)
  • Proper pagination and cursor support for schema listing

All changes maintain backward compatibility (schemaVersion parameters are optional) and follow existing API documentation patterns.

internal/errors.go (1)

91-126: New schema/account error types look consistent and usable

ErrInvalidSchema and ErrInvalidAccount follow the established error pattern in this file, integrate cleanly with errors.Is, and their messages match the expectations in the chart tests. No issues from my side here.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
internal/chart.go (1)

10-19: ChartAccountRules cannot currently be represented in JSON

ChartAccountRules is an empty struct, and marshalJsonObject only emits RULES_KEY when s.Account.Rules != (ChartAccountRules{}), which is never true for an empty struct. As a result:

  • .rules in input JSON is accepted and unmarshalled, but any information is discarded (there are no fields).
  • .rules is never serialized back out, so charts do not roundtrip this key.

If rules are intentionally a future stub, this is fine behavior, but it might be clearer to either:

  • Reject .rules altogether until fields are added, or
  • Add a presence flag on ChartAccount (e.g. rulesPresent bool) and emit RULES_KEY based on that flag once rules are actually modeled.

This avoids surprising users who expect rules to persist in stored schemas.

Also applies to: 208-215

internal/README.md (1)

253-257: Reconfirm intended divergence between chart segment regex and account-address regex

ChartSegmentRegexp / ValidateSegment are documented as allowing - in segment names (^(\$|\.)?[a-zA-Z0-9_-]+$). The HTTP API account-address regex (in the API docs/routes) still only allows \w ([A-Za-z0-9_]), so charts can define segments that can never be referenced by valid account addresses.

If that’s not intentional, consider either:

  • Removing - from ChartSegmentRegexp, or
  • Broadening the account-address regex and updating the HTTP/API docs accordingly.

Otherwise, it’s worth explicitly calling out this divergence in the docs so users understand which chart paths are addressable.

Also applies to: 319-325

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f67c560 and 53f2b28.

📒 Files selected for processing (3)
  • internal/README.md (51 hunks)
  • internal/chart.go (1 hunks)
  • internal/chart_test.go (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • internal/chart_test.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-04-29T11:24:28.923Z
Learnt from: gfyrag
Repo: formancehq/ledger PR: 892
File: internal/controller/ledger/controller_default.go:196-196
Timestamp: 2025-04-29T11:24:28.923Z
Learning: In the ledger Import function, it's critical to maintain proper log ID tracking by updating lastLogID with the current log.ID after each processed log, rather than setting it to nil. This ensures the system can properly validate the ordering of logs and prevent duplicate or out-of-order processing, which is essential for maintaining data integrity in the ledger.

Applied to files:

  • internal/README.md
🧬 Code graph analysis (1)
internal/chart.go (1)
internal/errors.go (1)
  • ErrInvalidAccount (103-108)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (1)
internal/README.md (1)

253-257: Confirm intended difference between ChartSegmentRegexp and HTTP account-address regex

The docs expose:

var ChartSegmentRegexp = regexp.MustCompile(`^(\$|\.)?[a-zA-Z0-9_-]+$`)

which allows - in segment names. Earlier in this README / HTTP API docs, account addresses are validated with ^\w+(:\w+)*$, which does not allow -. That means you can define chart segments that cannot be addressed over the HTTP API.

If this divergence is not intentional, it would be good to pick one canonical rule and align both the code (internal/chart.go) and the documented address regex (e.g. either drop - from ChartSegmentRegexp or broaden the HTTP regex). Since this was raised in a prior review, just calling it out again for confirmation.

🧹 Nitpick comments (2)
internal/chart.go (2)

10-19: .rules is currently accepted but effectively ignored and never re-emitted

Right now:

  • ChartAccountRules is an empty struct.
  • ChartSegment.UnmarshalJSON happily unmarshals RULES_KEY into account.Rules.
  • marshalJsonObject only emits RULES_KEY when s.Account.Rules != (ChartAccountRules{}), which is never true for an empty struct; you even note “Never emitted for now”.

Combined with json.Unmarshal ignoring unknown fields on an empty struct, any .rules object in the input schema is silently discarded and will not roundtrip, while the public API/doc surface exposes ChartAccountRules and RULES_KEY.

If .rules is not meant to be usable yet, consider being explicit and failing fast when it appears, instead of accepting and dropping it. If you do want to preserve it (even before enforcing anything), a small presence flag on ChartAccount keeps roundtrip behavior predictable, e.g.:

 type ChartAccount struct {
     Metadata map[string]ChartAccountMetadata
-    Rules    ChartAccountRules
+    Rules    ChartAccountRules
+
+    hasRules bool
 }
@@
-        } else if key == RULES_KEY {
-            err := json.Unmarshal(value, &account.Rules)
+        } else if key == RULES_KEY {
+            account.hasRules = true
+            err := json.Unmarshal(value, &account.Rules)
             if err != nil {
                 return fmt.Errorf("invalid account rules: %v", err)
             }
@@
-        if s.Account.Rules != (ChartAccountRules{}) {
-            out[RULES_KEY] = s.Account.Rules
-        }
+        if s.Account.hasRules {
+            out[RULES_KEY] = s.Account.Rules
+        }

That way schemas using .rules won’t see their configuration silently disappear, and you have a clear hook for future rule fields.

Also applies to: 162-167, 215-222


106-120: Avoid recompiling variable-segment regex on every account lookup

During unmarshal you already validate the pattern with regexp.Compile(pat), but in findAccountSchema you use:

matches, err = regexp.Match(*variableSegment.Pattern, []byte(nextSegment))

which recompiles the same pattern for every lookup. If ValidatePosting is on a hot path, this becomes unnecessary overhead.

Consider caching the compiled regexp on ChartVariableSegment at unmarshal time and reusing it here, e.g. by adding an unexported *regexp.Regexp field and calling re.MatchString(nextSegment) instead. This keeps validation behavior identical while making account resolution cheaper under load.

Also applies to: 267-273

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 53f2b28 and c0be7da.

📒 Files selected for processing (3)
  • internal/README.md (51 hunks)
  • internal/chart.go (1 hunks)
  • internal/chart_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/chart_test.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-04-29T11:24:28.923Z
Learnt from: gfyrag
Repo: formancehq/ledger PR: 892
File: internal/controller/ledger/controller_default.go:196-196
Timestamp: 2025-04-29T11:24:28.923Z
Learning: In the ledger Import function, it's critical to maintain proper log ID tracking by updating lastLogID with the current log.ID after each processed log, rather than setting it to nil. This ensures the system can properly validate the ordering of logs and prevent duplicate or out-of-order processing, which is essential for maintaining data integrity in the ledger.

Applied to files:

  • internal/README.md
🧬 Code graph analysis (1)
internal/chart.go (2)
internal/storage/ledger/logs.go (1)
  • RawMessage (38-38)
internal/errors.go (1)
  • ErrInvalidAccount (103-108)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/controller/ledger/controller_default_test.go (1)

133-138: Fix InsertLog mock return type to match Store interface.

The DoAndReturn closure returns any and log, but Store.InsertLog returns error. This should be consistent with other InsertLog mocks in this test file.

Apply this diff:

-	DoAndReturn(func(_ context.Context, log *ledger.Log) any {
+	DoAndReturn(func(_ context.Context, log *ledger.Log) error {
 		log.ID = pointer.For(uint64(0))
-		return log
+		return nil
 	})
♻️ Duplicate comments (2)
internal/controller/ledger/controller_default_test.go (1)

72-79: Fix InsertLog mock return type to match Store interface.

The DoAndReturn closure returns any and log, but Store.InsertLog has signature func(context.Context, *ledger.Log) error. This is inconsistent with other InsertLog mocks in this file (see lines 201-205, 250-254, 297-301) which correctly return error.

Apply this diff:

-		DoAndReturn(func(_ context.Context, log *ledger.Log) any {
+		DoAndReturn(func(_ context.Context, log *ledger.Log) error {
 			log.ID = pointer.For(uint64(0))
-			return log
+			return nil
 		})
internal/controller/ledger/controller_default.go (1)

509-509: Critical: Reverted transactions bypass schema validation.

Line 509 passes nil as the schema to CommitTransaction even though _schema is available as a parameter (line 454). This means reverted transactions don't validate their postings against the chart of accounts, which could allow invalid account references during reversals.

Apply this diff to enforce schema validation on reverted transactions:

-	err = store.CommitTransaction(ctx, nil, &reversedTx, nil)
+	err = store.CommitTransaction(ctx, _schema, &reversedTx, nil)
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between c0be7da and 76c35f2.

⛔ Files ignored due to path filters (5)
  • openapi.yaml is excluded by !**/*.yaml
  • openapi/v2.yaml is excluded by !**/*.yaml
  • pkg/client/.speakeasy/gen.lock is excluded by !**/*.lock, !**/*.lock
  • pkg/client/.speakeasy/logs/naming.log is excluded by !**/*.log, !**/*.log
  • pkg/client/speakeasyusagegen/.speakeasy/logs/naming.log is excluded by !**/*.log, !**/*.log
📒 Files selected for processing (31)
  • docs/api/README.md (25 hunks)
  • internal/README.md (51 hunks)
  • internal/api/bulking/bulker.go (11 hunks)
  • internal/api/bulking/bulker_test.go (1 hunks)
  • internal/api/bulking/mocks_ledger_controller_test.go (3 hunks)
  • internal/api/common/mocks_ledger_controller_test.go (3 hunks)
  • internal/api/v1/mocks_ledger_controller_test.go (3 hunks)
  • internal/api/v2/controllers_schema_insert.go (1 hunks)
  • internal/api/v2/controllers_schema_insert_test.go (1 hunks)
  • internal/api/v2/mocks_ledger_controller_test.go (3 hunks)
  • internal/controller/ledger/controller.go (2 hunks)
  • internal/controller/ledger/controller_default.go (12 hunks)
  • internal/controller/ledger/controller_default_test.go (8 hunks)
  • internal/controller/ledger/controller_generated_test.go (3 hunks)
  • internal/controller/ledger/controller_with_events.go (1 hunks)
  • internal/controller/ledger/controller_with_too_many_client_handling.go (2 hunks)
  • internal/controller/ledger/controller_with_traces.go (3 hunks)
  • internal/controller/ledger/log_process.go (3 hunks)
  • internal/controller/ledger/log_process_test.go (5 hunks)
  • internal/controller/system/state_tracker.go (1 hunks)
  • internal/log.go (13 hunks)
  • pkg/client/docs/models/operations/v2insertschemarequest.md (1 hunks)
  • pkg/client/docs/models/operations/v2insertschemaresponse.md (1 hunks)
  • pkg/client/docs/models/operations/v2reverttransactionrequest.md (1 hunks)
  • pkg/client/models/operations/v2addmetadataontransaction.go (2 hunks)
  • pkg/client/models/operations/v2addmetadatatoaccount.go (2 hunks)
  • pkg/client/models/operations/v2createtransaction.go (2 hunks)
  • pkg/client/models/operations/v2insertschema.go (1 hunks)
  • pkg/client/models/operations/v2reverttransaction.go (2 hunks)
  • pkg/client/v2.go (1 hunks)
  • test/e2e/api_idempotency_hit_header_test.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (10)
  • pkg/client/models/operations/v2addmetadataontransaction.go
  • pkg/client/models/operations/v2reverttransaction.go
  • pkg/client/docs/models/operations/v2reverttransactionrequest.md
  • pkg/client/models/operations/v2insertschema.go
  • pkg/client/models/operations/v2createtransaction.go
  • pkg/client/docs/models/operations/v2insertschemarequest.md
  • internal/api/v2/controllers_schema_insert_test.go
  • pkg/client/models/operations/v2addmetadatatoaccount.go
  • internal/controller/ledger/controller_generated_test.go
  • internal/api/v2/controllers_schema_insert.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-04-29T11:24:28.923Z
Learnt from: gfyrag
Repo: formancehq/ledger PR: 892
File: internal/controller/ledger/controller_default.go:196-196
Timestamp: 2025-04-29T11:24:28.923Z
Learning: In the ledger Import function, it's critical to maintain proper log ID tracking by updating lastLogID with the current log.ID after each processed log, rather than setting it to nil. This ensures the system can properly validate the ordering of logs and prevent duplicate or out-of-order processing, which is essential for maintaining data integrity in the ledger.

Applied to files:

  • internal/controller/ledger/controller_default.go
  • internal/README.md
🧬 Code graph analysis (14)
internal/controller/ledger/log_process_test.go (4)
internal/controller/ledger/store.go (1)
  • Store (29-67)
internal/schema.go (1)
  • Schema (15-21)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/log.go (1)
  • CreatedTransaction (213-216)
internal/controller/ledger/controller_with_too_many_client_handling.go (3)
internal/controller/ledger/controller.go (1)
  • InsertSchema (135-138)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (26-29)
internal/controller/ledger/controller_with_traces.go (5)
internal/controller/ledger/controller.go (1)
  • InsertSchema (135-138)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
pkg/events/message.go (1)
  • InsertedSchema (77-80)
internal/tracing/tracing.go (1)
  • TraceWithMetric (101-129)
internal/schema.go (1)
  • Schema (15-21)
internal/controller/system/state_tracker.go (3)
internal/controller/ledger/controller.go (2)
  • InsertSchema (135-138)
  • Controller (21-89)
internal/log.go (2)
  • Log (89-102)
  • InsertedSchema (416-418)
internal/controller/system/controller.go (1)
  • Controller (41-55)
test/e2e/api_idempotency_hit_header_test.go (3)
pkg/client/models/operations/v2insertschema.go (1)
  • V2InsertSchemaRequest (9-17)
pkg/client/models/components/v2schemadata.go (1)
  • V2SchemaData (6-9)
pkg/client/models/components/v2chartsegment.go (1)
  • V2ChartSegment (13-19)
internal/controller/ledger/log_process.go (4)
internal/controller/ledger/store.go (1)
  • Store (29-67)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/log.go (1)
  • Log (89-102)
internal/controller/ledger/errors.go (2)
  • ErrSchemaNotFound (249-252)
  • ErrSchemaNotSpecified (288-290)
internal/api/bulking/bulker.go (1)
internal/api/bulking/elements.go (3)
  • Bulk (22-22)
  • BulkElementResult (73-78)
  • BulkElement (24-28)
internal/api/bulking/bulker_test.go (1)
internal/machine/vm/run.go (1)
  • Run (57-87)
internal/api/v2/mocks_ledger_controller_test.go (5)
internal/api/common/mocks_ledger_controller_test.go (2)
  • LedgerController (26-30)
  • LedgerControllerMockRecorder (33-35)
internal/controller/ledger/controller.go (1)
  • InsertSchema (135-138)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
pkg/events/message.go (1)
  • InsertedSchema (77-80)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (26-29)
internal/log.go (1)
pkg/events/message.go (4)
  • SavedMetadata (27-32)
  • DeletedMetadata (60-65)
  • RevertedTransaction (44-48)
  • InsertedSchema (77-80)
internal/controller/ledger/controller_default_test.go (5)
internal/schema.go (2)
  • Schema (15-21)
  • SchemaData (11-13)
internal/chart.go (3)
  • ChartOfAccounts (40-40)
  • ChartAccount (16-19)
  • ChartAccountMetadata (12-14)
internal/controller/ledger/controller.go (3)
  • InsertSchema (135-138)
  • CreateTransaction (102-106)
  • RunScript (91-91)
internal/log.go (2)
  • Log (89-102)
  • InsertedSchemaLogType (25-25)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/api/common/mocks_ledger_controller_test.go (4)
internal/schema.go (1)
  • Schema (15-21)
internal/storage/ledger/main_test.go (1)
  • T (75-79)
internal/controller/ledger/controller.go (1)
  • InsertSchema (135-138)
pkg/events/message.go (1)
  • InsertedSchema (77-80)
internal/api/bulking/mocks_ledger_controller_test.go (9)
internal/api/common/mocks_ledger_controller_test.go (2)
  • LedgerController (26-30)
  • LedgerControllerMockRecorder (33-35)
internal/api/v1/mocks_ledger_controller_test.go (2)
  • LedgerController (26-30)
  • LedgerControllerMockRecorder (33-35)
internal/api/v2/mocks_ledger_controller_test.go (2)
  • LedgerController (26-30)
  • LedgerControllerMockRecorder (33-35)
internal/schema.go (1)
  • Schema (15-21)
internal/controller/ledger/controller.go (1)
  • InsertSchema (135-138)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/storage/ledger/logs.go (1)
  • Log (20-26)
pkg/events/message.go (1)
  • InsertedSchema (77-80)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (26-29)
internal/api/v1/mocks_ledger_controller_test.go (9)
internal/api/bulking/mocks_ledger_controller_test.go (3)
  • LedgerController (26-30)
  • LedgerControllerMockRecorder (33-35)
  • LedgerControllerGetSchemaCall (498-500)
internal/api/common/mocks_ledger_controller_test.go (2)
  • LedgerController (26-30)
  • LedgerControllerMockRecorder (33-35)
internal/api/v2/mocks_ledger_controller_test.go (3)
  • LedgerController (26-30)
  • LedgerControllerMockRecorder (33-35)
  • LedgerControllerGetSchemaCall (498-500)
internal/schema.go (1)
  • Schema (15-21)
internal/controller/ledger/controller.go (1)
  • InsertSchema (135-138)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/storage/ledger/logs.go (1)
  • Log (20-26)
pkg/events/message.go (1)
  • InsertedSchema (77-80)
internal/storage/common/pagination.go (1)
  • PaginatedQuery (26-29)
🪛 LanguageTool
docs/api/README.md

[grammar] ~5786-~5786: Ensure spelling is correct
Context: ...tructure with metadata ### Properties allOf |Name|Type|Required|Restrictions|Descrip...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🪛 markdownlint-cli2 (0.18.1)
docs/api/README.md

359-359: Blank line inside blockquote

(MD028, no-blanks-blockquote)


411-411: Blank line inside blockquote

(MD028, no-blanks-blockquote)


467-467: Heading levels should only increment by one level at a time
Expected: h3; Actual: h4

(MD001, heading-increment)


476-476: Blank line inside blockquote

(MD028, no-blanks-blockquote)


5652-5652: Multiple headings with the same content

(MD024, no-duplicate-heading)


5654-5654: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


5670-5670: Multiple headings with the same content

(MD024, no-duplicate-heading)


5696-5696: Multiple headings with the same content

(MD024, no-duplicate-heading)


5727-5727: Multiple headings with the same content

(MD024, no-duplicate-heading)


5755-5755: Multiple headings with the same content

(MD024, no-duplicate-heading)


5785-5785: Multiple headings with the same content

(MD024, no-duplicate-heading)


5825-5825: Multiple headings with the same content

(MD024, no-duplicate-heading)


5863-5863: Multiple headings with the same content

(MD024, no-duplicate-heading)


5899-5899: Multiple headings with the same content

(MD024, no-duplicate-heading)

pkg/client/docs/models/operations/v2insertschemaresponse.md

9-9: Reference links and images should use a label that is defined
Missing link or image reference definition: "string"

(MD052, reference-links-images)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: GoReleaser
  • GitHub Check: Dirty
  • GitHub Check: Tests
🔇 Additional comments (42)
pkg/client/docs/models/operations/v2insertschemaresponse.md (1)

6-9: Documentation accurately reflects the actual V2InsertSchemaResponse struct.

The Go struct in pkg/client/models/operations/v2insertschema.go defines V2InsertSchemaResponse with exactly two fields: HTTPMeta and Headers. No V2SchemaResponse field exists. The table documentation is correct. However, the Headers field type formatting in the documentation appears incorrect—it displays as map[string][]*string* (with asterisks that create unintended markdown formatting) when it should be map[string][]string.

Likely an incorrect or invalid review comment.

test/e2e/api_idempotency_hit_header_test.go (1)

479-525: LGTM! Idempotency tests for InsertSchema are well-structured.

The test block correctly validates the idempotency behavior of the new V2 InsertSchema API:

  • First call with an idempotency key returns a response without the Idempotency-Hit header
  • Subsequent calls with the same key return the cached response with the header set to "true"

The tests follow the established pattern used for other idempotent operations in this suite and provide appropriate coverage for the new schema insertion functionality.

internal/api/v1/mocks_ledger_controller_test.go (1)

481-518: LGTM! Generated mock code follows consistent patterns.

The three new mock methods (GetSchema, InsertSchema, ListSchemas) are properly generated with the standard gomock structure including Return, Do, and DoAndReturn helpers. The signatures align with the Controller interface additions.

Also applies to: 713-752, 871-908

internal/controller/ledger/controller_with_events.go (1)

156-168: LGTM! Consistent event handling pattern.

The InsertSchema implementation correctly follows the established pattern used by other methods (CreateTransaction, RevertTransaction, etc.):

  • Delegates to underlying controller
  • Emits InsertedSchema event via handleEvent
  • Respects DryRun flag
  • Returns all expected values including idempotencyHit
internal/controller/system/state_tracker.go (1)

199-211: LGTM! Consistent state tracking pattern.

The InsertSchema method correctly uses the handleState wrapper pattern, matching the implementation of CreateTransaction, RevertTransaction, and other state-tracked operations. It properly propagates the DryRun parameter and returns all expected values.

internal/api/bulking/bulker_test.go (1)

433-433: LGTM! Test updated for new Run signature.

The empty string parameter aligns with the extended Run signature that now accepts an id/schemaVersion parameter. Using an empty string for tests is appropriate when schema versioning is not relevant to the test case.

internal/controller/ledger/log_process_test.go (2)

35-37: LGTM! Test updated for schema-aware forgeLog.

The test correctly adds the FindLatestSchemaVersion mock expectation and updates the callback signature to include the schema parameter, aligning with the schema-aware forgeLog implementation.

Also applies to: 52-52


70-72: LGTM! Deadlock test updated for schema-aware retry flow.

Both retry attempts in the deadlock scenario now correctly mock FindLatestSchemaVersion and use the updated callback signature with the schema parameter.

Also applies to: 83-85, 100-100

internal/controller/ledger/controller.go (1)

83-88: LGTM! Clean schema API additions.

The three new methods (InsertSchema, GetSchema, ListSchemas) and the InsertSchema parameter type provide a clear and consistent API surface for schema management. The signatures align with existing patterns in the Controller interface.

Also applies to: 135-138

internal/controller/ledger/controller_with_too_many_client_handling.go (1)

133-146: LGTM! Consistent retry handling for schema operations.

All three new methods (InsertSchema, GetSchema, ListSchemas) correctly use the handleRetry wrapper to provide automatic retry on ErrTooManyClient, consistent with the existing pattern used by CreateTransaction, RevertTransaction, and other methods.

Also applies to: 148-159, 161-172

internal/controller/ledger/controller_default_test.go (1)

25-88: Well-structured parameterized test for schema support.

The refactoring to use a testCreateTransaction helper with withSchema parameter provides good test coverage for both schema and non-schema flows. The schema setup (lines 38-88) properly exercises the InsertSchema path, and the conditional mock expectations (lines 111-129) correctly differentiate between the two scenarios.

Also applies to: 111-153

internal/api/common/mocks_ledger_controller_test.go (1)

217-230: Schema-related mocks correctly mirror the Controller interface

The new GetSchema, InsertSchema, and ListSchemas mock methods (and their recorder helpers) match the controller signatures and follow the existing gomock pattern; no issues from a testing surface perspective.

Also applies to: 305-320, 367-380

pkg/client/v2.go (1)

656-853: V2 schema client methods align with existing SDK patterns

InsertSchema, GetSchema, and ListSchemas use the expected routes, HTTP verbs, retry/timeout handling, and JSON (de)serialization patterns consistent with the rest of the V2 client; signatures and response wiring look correct.

Also applies to: 855-1061, 1063-1273

internal/controller/ledger/controller_with_traces.go (2)

47-49: Schema histograms are correctly added and initialized

The new insert/get/list schema histograms are added to the struct and initialized with consistent names and metric.WithUnit("ms"), matching the rest of the controller latency metrics.

Also applies to: 155-166


519-585: Traced schema methods correctly wrap the underlying controller

InsertSchema, GetSchema, and ListSchemas follow the existing TraceWithMetric pattern, use span names aligned with method names, and correctly propagate logs, results, idempotency flags, and errors.

internal/api/v2/mocks_ledger_controller_test.go (3)

481-518: LGTM! Generated mock follows standard gomock pattern.

The GetSchema mock method correctly implements the standard gomock pattern with proper type assertions and chainable call wrappers.


713-752: LGTM! Generated mock follows standard gomock pattern.

The InsertSchema mock method correctly implements the standard gomock pattern with proper type assertions for all four return values.


871-908: LGTM! Generated mock follows standard gomock pattern.

The ListSchemas mock method correctly implements the standard gomock pattern with proper generic cursor type handling.

internal/api/bulking/bulker.go (3)

30-91: LGTM! SchemaVersion parameter properly threaded through bulk processing.

The run method correctly adds the schemaVersion parameter and propagates it to processElement at line 64.


93-132: LGTM! Public API updated to accept schemaVersion.

The Run method correctly adds the schemaVersion parameter and propagates it through to the internal run method.


134-266: LGTM! SchemaVersion consistently propagated to all ledger operations.

The processElement method correctly adds schemaVersion and consistently propagates it to all controller operations via the Parameters struct.

internal/controller/ledger/log_process.go (1)

65-127: LGTM! Schema resolution and validation properly implemented.

The schema resolution logic correctly:

  • Fetches the schema when SchemaVersion is provided
  • Returns informative errors (ErrSchemaNotFound, ErrSchemaNotSpecified) with latest version context
  • Only requires schema when the payload explicitly needs it via NeedsSchema()
  • Validates the log against the schema before insertion
internal/log.go (6)

20-86: LGTM! InsertedSchemaLogType properly integrated.

The new log type constant is correctly integrated into the String() and LogTypeFromString() methods with consistent "INSERTED_SCHEMA" string representation.


88-183: LGTM! SchemaVersion field properly integrated into Log structure and hash computation.

The SchemaVersion field is correctly added with appropriate JSON/bun tags and properly included in the hash computation to maintain log integrity.


218-229: LGTM! CreatedTransaction properly validates postings against schema.

The ValidateWithSchema implementation correctly iterates through all postings and validates them against the chart of accounts.


272-277: Metadata operations require schema context but skip validation.

SavedMetadata returns true for NeedsSchema() but performs no validation in ValidateWithSchema(). This appears intentional—metadata operations need schema context for consistency but don't validate against the chart of accounts.


416-431: LGTM! InsertedSchema properly implements LogPayload interface.

The InsertedSchema type correctly returns false for NeedsSchema() since schema insertion doesn't require a pre-existing schema. All interface requirements are properly implemented.


433-455: LGTM! HydrateLog properly handles InsertedSchemaLogType.

The hydration logic correctly handles the new InsertedSchemaLogType by instantiating an InsertedSchema payload, consistent with other log types.

internal/api/bulking/mocks_ledger_controller_test.go (1)

481-518: LGTM! All generated mocks follow standard gomock patterns.

The three new mock methods (GetSchema, InsertSchema, ListSchemas) are correctly generated with proper type assertions and chainable call wrappers, consistent with existing mock patterns in the file.

Also applies to: 713-752, 871-908

internal/controller/ledger/controller_default.go (7)

57-76: LGTM! InsertSchema properly handles duplicate schemas and errors.

The implementation correctly:

  • Uses the logProcessor pattern for consistency
  • Handles constraint violations as ErrSchemaAlreadyExists
  • Propagates errors from NewSchema and InsertSchema
  • Returns the complete InsertedSchema payload

78-84: LGTM! Schema retrieval methods properly delegate to store.

The GetSchema and ListSchemas methods correctly delegate to the store layer without unnecessary intermediate logic.


381-448: LGTM! Schema properly propagated through transaction creation.

The createTransaction method correctly accepts and passes the schema to CommitTransaction for validation.


524-534: LGTM! Metadata methods consistently handle schema parameter.

The transaction metadata methods accept but don't use the schema parameter, which is consistent with the design where metadata operations need schema context but don't perform validation. The underscore prefix correctly indicates the unused parameter.

Also applies to: 562-577


541-554: LGTM! Account metadata properly validates against schema.

The saveAccountMetadata method correctly passes the schema to UpsertAccounts, enabling account validation during metadata operations.


271-349: LGTM! Import correctly handles schema insertion and replays validated logs.

The importLog method:

  • Properly handles InsertedSchema payloads by inserting the schema
  • Correctly passes nil schema during import since these are replayed logs that were already validated at creation time
  • Maintains historical log integrity by not re-validating during import

127-166: LGTM! Schema log processor properly initialized.

The insertSchemaLp is correctly initialized in NewDefaultController following the same pattern as other log processors.

docs/api/README.md (2)

318-514: ✓ Schema management endpoints well-structured; however, markdown linting violations require resolution.

The three new schema endpoints (insert, get, list) are properly documented with consistent structure, correct parameter tables, and example payloads. The schemaVersion parameter integration across bulk and transaction endpoints (lines 738, 1143, 1582, 1864, 1936) maintains consistency with the new schema operations.

However, static analysis identifies persistent markdown linting violations that were reportedly addressed in past commits but still appear:

  • MD028 (blank lines in blockquotes): Lines 359, 411, 467, 476
  • MD024 (duplicate headings): Schema type definitions (lines 5652, 5670, 5696, 5727, 5755, 5785, 5825, 5863, 5899)
  • MD036 (emphasis as heading): Line 5654

Since this file is auto-generated by Widdershins, regenerate the documentation from the OpenAPI source to resolve these linting violations, or update the generator template/markdownlint configuration to suppress these rules if they're unavoidable in the generated output.


4089-4089: ✓ INSERTED_SCHEMA log type enum correctly added.

The new INSERTED_SCHEMA enum value at line 4089 correctly represents the schema insertion event type, aligning with the InsertedSchema log payload type. This completes the log type coverage for schema operations.

internal/README.md (4)

213-241: Schema property constants and regex pattern documented; verify source-level alignment of segment validation rules.

The new constants (PROPERTY_PREFIX, PATTERN_KEY, RULES_KEY, SELF_KEY, METADATA_KEY) and ChartSegmentRegexp variable are properly documented. However, the regex pattern at line 256—^(\$|\.)?[a-zA-Z0-9_-]+$—allows hyphens in segment names, while the HTTP API's account address regex (^\w+(:\w+)*$ documented in docs/api/README.md) only permits word characters [A-Za-z0-9_].

This divergence means chart segments can be defined with hyphens that will never correspond to valid account addresses. Verify with the code maintainers whether this is intentional or if the regex patterns should be aligned (either remove - from ChartSegmentRegexp or broaden the HTTP API address regex). If the divergence is intentional, document the constraint explicitly.

Also applies to: 253-257


1075-1075: ✓ Schema validation integration in Log type and LogPayload interface is complete.

The addition of SchemaVersion field to the Log struct (line 1075), the NeedsSchema() and ValidateWithSchema() methods to the LogPayload interface (lines 1159–1160), and the InsertedSchemaLogType constant (line 1190) properly establish schema-aware validation throughout the logging pipeline. The documentation correctly reflects the public API surface.

Also applies to: 1159-1160, 1190-1190


420-547: ✓ Chart-related types and InsertedSchema payload properly documented.

New public types—ChartAccount, ChartAccountMetadata, ChartAccountRules, ChartOfAccounts, ChartSegment, ChartVariableSegment, and InsertedSchema—are thoroughly documented with their methods and usage. The recursive structure of ChartSegment (which references itself via FixedSegments and the unmarshal logic) is correctly reflected in the documentation.

Also applies to: 956-990, 1574-1607


59-70: ✓ New error types ErrInvalidAccount and ErrInvalidSchema properly exposed.

Both error types are documented with their Error() and Is() methods, enabling type-safe error handling for account validation and schema validation failures. This aligns with the expanded schema validation surface.

Also applies to: 68-835

Comment on lines +375 to +380
func (p RevertedTransaction) NeedsSchema() bool {
return true
}
func (r RevertedTransaction) ValidateWithSchema(schema Schema) error {
return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the context around RevertedTransaction and CreatedTransaction
rg -n 'type (Created|Reverted)Transaction' internal/log.go -A 10

Repository: formancehq/ledger

Length of output: 833


🏁 Script executed:

# Check CreatedTransaction's ValidateWithSchema implementation
rg -n 'type CreatedTransaction' internal/log.go -A 20

Repository: formancehq/ledger

Length of output: 654


🏁 Script executed:

# Search for all transaction types and their schema validation
rg -n 'ValidateWithSchema' internal/log.go -B 2

Repository: formancehq/ledger

Length of output: 758


🏁 Script executed:

# Look for posting validation logic
rg -n 'posting|Posting' internal/log.go | head -30

Repository: formancehq/ledger

Length of output: 359


🏁 Script executed:

# Check if there are tests that show expected behavior
fd -t f 'log_test\.go|_test\.go' | head -5

Repository: formancehq/ledger

Length of output: 247


RevertedTransaction.ValidateWithSchema() should validate postings like CreatedTransaction does.

RevertedTransaction returns true for NeedsSchema() but performs no validation in ValidateWithSchema(). Since RevertedTransaction contains a RevertTransaction with Postings (line 405), these should be validated against the chart of accounts, just as CreatedTransaction validates its postings (lines 222-227). The reversal transaction creates new postings that must conform to the schema.

Update RevertedTransaction.ValidateWithSchema() to validate the RevertTransaction.Postings:

func (r RevertedTransaction) ValidateWithSchema(schema Schema) error {
	for _, posting := range r.RevertTransaction.Postings {
		err := schema.Chart.ValidatePosting(posting)
		if err != nil {
			return err
		}
	}
	return nil
}
🤖 Prompt for AI Agents
In internal/log.go around lines 375 to 380,
RevertedTransaction.ValidateWithSchema currently returns nil despite
NeedsSchema() being true; update it to iterate over r.RevertTransaction.Postings
and call schema.Chart.ValidatePosting for each posting, returning the first
non-nil error and nil if all postings validate successfully so the reversal
postings are validated against the chart of accounts like CreatedTransaction
does.

| Field | Type | Required | Description |
| ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ | ------------------------------------------------------------------ |
| `HTTPMeta` | [components.HTTPMetadata](../../models/components/httpmetadata.md) | :heavy_check_mark: | N/A |
| `Headers` | map[string][]*string* | :heavy_check_mark: | N/A | No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix malformed markdown reference link syntax.

The markdown reference link []*string* is malformed—"string" is not defined as a reference target. This will render incorrectly in the documentation.

Apply this diff to fix the markdown syntax:

-| `Headers`                                                          | map[string][]*string*                                              | :heavy_check_mark:                                                 | N/A                                                                |
+| `Headers`                                                          | map[string]\*string                                                | :heavy_check_mark:                                                 | N/A                                                                |

Alternatively, if this field should link to another documentation page, ensure the reference is properly defined.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
| `Headers` | map[string][]*string* | :heavy_check_mark: | N/A |
| `Headers` | map[string]\*string | :heavy_check_mark: | N/A |
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

9-9: Reference links and images should use a label that is defined
Missing link or image reference definition: "string"

(MD052, reference-links-images)

🤖 Prompt for AI Agents
In pkg/client/docs/models/operations/v2insertschemaresponse.md around line 9,
the type column contains a malformed markdown reference `[]*string*`; replace it
with a literal type rendering (e.g., wrap the full type in inline code such as
`map[string][]*string`) so Markdown does not treat "string" as a reference
target, or if this was intended to link to another doc, change `[]*string*` to a
proper reference link and add the corresponding reference definition.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/controller/ledger/controller_default.go (1)

287-301: Import flow bypasses schema validation for transactions.

During import, transactions are committed with nil schema (lines 287 and 301), which bypasses any schema validation even though the import may include schema information from logs.

Consider whether imported transactions should be validated against their schemas if available, or add a comment explaining why validation is intentionally skipped during import.

♻️ Duplicate comments (1)
internal/controller/ledger/controller_default.go (1)

512-512: Revert transactions bypass schema validation.

Passing nil for the schema parameter means reverted transactions won't be validated against schema rules. This was flagged in a previous review and remains unaddressed.

If schema validation should apply to reverts, apply this diff:

-	err = store.CommitTransaction(ctx, nil, &reversedTx, nil)
+	err = store.CommitTransaction(ctx, _schema, &reversedTx, nil)

If reverts should intentionally bypass validation, please add a comment explaining why.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between bdb6c68 and 9838c3a.

📒 Files selected for processing (9)
  • cmd/config.go (1 hunks)
  • cmd/serve.go (3 hunks)
  • internal/controller/ledger/controller_default.go (14 hunks)
  • internal/controller/ledger/log_process.go (3 hunks)
  • internal/controller/ledger/log_process_test.go (5 hunks)
  • internal/controller/ledger/schema.go (1 hunks)
  • internal/controller/system/controller.go (3 hunks)
  • internal/controller/system/module.go (2 hunks)
  • pkg/testserver/server.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • internal/controller/ledger/log_process_test.go
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-04-29T11:24:28.923Z
Learnt from: gfyrag
Repo: formancehq/ledger PR: 892
File: internal/controller/ledger/controller_default.go:196-196
Timestamp: 2025-04-29T11:24:28.923Z
Learning: In the ledger Import function, it's critical to maintain proper log ID tracking by updating lastLogID with the current log.ID after each processed log, rather than setting it to nil. This ensures the system can properly validate the ordering of logs and prevent duplicate or out-of-order processing, which is essential for maintaining data integrity in the ledger.

Applied to files:

  • internal/controller/ledger/controller_default.go
🧬 Code graph analysis (8)
pkg/testserver/server.go (1)
cmd/serve.go (1)
  • BindFlag (61-61)
internal/controller/ledger/schema.go (1)
cmd/serve.go (1)
  • SchemaEnforcementMode (72-72)
cmd/config.go (3)
cmd/root.go (1)
  • ExperimentalExporters (21-21)
cmd/serve.go (2)
  • SemconvMetricsNames (71-71)
  • SchemaEnforcementMode (72-72)
internal/controller/ledger/schema.go (1)
  • SchemaEnforcementMode (3-3)
internal/controller/system/module.go (4)
cmd/serve.go (1)
  • SchemaEnforcementMode (72-72)
internal/controller/ledger/schema.go (1)
  • SchemaEnforcementMode (3-3)
internal/controller/ledger/controller_default.go (1)
  • WithSchemaEnforcementMode (624-628)
internal/controller/system/controller.go (1)
  • WithSchemaEnforcementMode (312-316)
cmd/serve.go (2)
internal/controller/ledger/schema.go (1)
  • SchemaEnforcementMode (3-3)
pkg/client/formance.go (1)
  • String (30-30)
internal/controller/system/controller.go (3)
cmd/serve.go (1)
  • SchemaEnforcementMode (72-72)
internal/controller/ledger/schema.go (1)
  • SchemaEnforcementMode (3-3)
internal/controller/ledger/controller_default.go (2)
  • WithSchemaEnforcementMode (624-628)
  • DefaultController (34-57)
internal/controller/ledger/log_process.go (5)
internal/controller/ledger/schema.go (2)
  • SchemaEnforcementMode (3-3)
  • SchemaEnforcementStrict (7-7)
internal/log.go (2)
  • LogPayload (201-205)
  • Log (89-102)
internal/storage/ledger/store.go (1)
  • Store (25-50)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/controller/ledger/errors.go (3)
  • ErrNotFound (14-14)
  • ErrSchemaNotFound (249-252)
  • ErrSchemaNotSpecified (288-290)
internal/controller/ledger/controller_default.go (6)
internal/controller/ledger/schema.go (2)
  • SchemaEnforcementMode (3-3)
  • SchemaEnforcementAudit (9-9)
internal/controller/ledger/controller.go (4)
  • RevertTransaction (108-113)
  • DeleteTransactionMetadata (125-128)
  • DeleteAccountMetadata (130-133)
  • InsertSchema (135-138)
internal/controller/system/controller.go (2)
  • DefaultController (57-75)
  • WithSchemaEnforcementMode (312-316)
internal/controller/ledger/parameters.go (1)
  • Parameters (3-8)
internal/controller/ledger/store.go (1)
  • Store (29-67)
internal/schema.go (1)
  • Schema (15-21)
🔇 Additional comments (2)
internal/controller/ledger/controller_default.go (2)

59-78: LGTM! Clean schema insertion with duplicate handling.

The InsertSchema implementation properly handles duplicate schema versions by catching constraint violations and returning a specific error type.


143-143: Good default choice for schema enforcement mode.

Defaulting to SchemaEnforcementAudit is a sensible choice that allows observability without breaking existing functionality, enabling gradual migration to strict enforcement.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants