Skip to content

client.reports.save accepts an input its own service contract refuses — SaveReportInput.query is required and neither the SDK nor the route checks it #11926

Description

@os-zhuang

Found while implementing #8140, which narrowed return types only. This is on the input side
and was deliberately not ridden into that PR.

Measured at 1f6d04703.

What is wrong

client.reports.save declares its parameter as any:

save: async (report: any): Promise< SavedReport > => {
    const res = await this.fetch(`${this.baseUrl}/api/v1/reports`, {
        method: 'POST', body: JSON.stringify(report ?? {}),
    });
    return this.unwrapResponse< SavedReport >(res);
},

The type the route's service method actually takes is SaveReportInput
(packages/spec/src/contracts/report-service.ts:89-97), on which query: ReportQuery is
required
:

export interface SaveReportInput {
  id?: string;
  name: string;
  description?: string;
  object: string;
  query: ReportQuery;      // ← required
  format?: ReportFormat;
  ownerId?: string;
}

And the route does not check it either — POST /reports forwards the body straight through
(packages/rest/src/rest-server.ts:10293):

const row = await svc.saveReport(req.body ?? {}, context ?? {});

So a caller can send a report definition with no query at all; whether that is refused, stored
half-built, or throws depends entirely on which reports implementation is mounted.

How it was measured — this is not a code-reading claim

While implementing #8140 I briefly bound the parameter to SaveReportInput. tsc immediately
failed on this repo's own test, packages/client/src/client.test.ts:352:

src/client.test.ts(352,35): error TS2345: Argument of type '{ name: string; object: string; }'
  is not assignable to parameter of type 'SaveReportInput'.
  Property 'query' is missing in type '{ name: string; object: string; }' but required in
  type 'SaveReportInput'.

That call is client.reports.save({ name: 'Pipeline', object: 'lead' }) — a fixture that has been
constructing an input the contract refuses, invisibly, because the parameter is any. I reverted
the parameter narrowing (out of #8140's declared scope) rather than adjust the fixture to make a
type error go away, since the fixture is evidence, not noise.

⚠️ The fixture is not itself the defect and should not be "fixed" alone: it is a mock-transport
test that never reaches a real service, so adding a query to it silences the signal without
closing anything. The defect is that nothing on the path — SDK parameter, route, or a schema at the
door — states the requirement.

Adjacent, same class

Seven public SDK methods take a parameter typed any (packages/client/src/index.ts):

$ grep -nE '^\s+[a-zA-Z_]+: async \(.*: any[,)]' packages/client/src/index.ts
1161:    query: async (payload: any) => {
1185:    explain: async (payload: any) => {
3039:    upload: async (file: any, scope: string = 'user'): Promise<FileUploadResponse> => {
3264:      trigger: async (triggerName: string, payload: any) => {
3302:      create: async (name: string, definition: any): Promise<any> => {
3318:      update: async (name: string, definition: any): Promise<any> => {
4185:    save: async (report: any): Promise<SavedReport> => {

reports.save is the one with a measured consequence, so it is the subject of this card; the
other six are listed so a fix can consider whether the same reachable-contract argument applies
(automation.create / update at 3302 / 3318 are the input half of #11924's routes).

Why it matters beyond typing

This is the contract-first shape from AGENTS.md Prime Directive #12 read from the producer side: an
authoring surface that accepts off-spec input and passes it to a service that requires more. The
right fix is to reject it loudly at the door — a schema at the route, or the parameter typed to the
contract, or both — rather than to keep the SDK permissive and let each reports implementation
decide what a query-less report means.


Generated by Claude Code

Activity

  1. added theissue type on Aug 25, 2026
  2. claude commented on Aug 25, 2026

    @claude
    Contributor

    Triage: lands in packages/client/src/index.ts (bind reports.save input) plus the packages/rest route seam; domain:cli. → pm:queue, Bug — declared≠enforced: SaveReportInput.query is required by the service contract and neither the SDK nor the route checks it (measured via tsc against the repo's own test). Clause-②: yes — restoring enforcement changes accept/reject behavior at the route; the claim must declare it and dispatch runs at the contract-review tier. Hard serial: #8140 is in flight on the same file — dispatch only after its PR merges. Note for the dev: client.test.ts:352 sending no query is part of the fix surface, not evidence the shape is legal.


    Generated by Claude Code

  3. self-assigned this
    on Aug 26, 2026
  4. os-litant commented on Aug 26, 2026

    @os-litant
    Collaborator

    Claim: PM loop round R39
    Session: session_01UjujZN219uFzBhSYfMykCd
    Branch: claude/issue-11926-reports-save-input-contract
    Worktree: directory objectstack-issue-11926
    Domain: domain:cli
    File surface: packages/client/src/index.ts (the reports.save method), packages/rest/src/rest-server.ts (the POST /reports route), and their sibling tests (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus — under the standing quota exemption, ⛔ not a discretionary downgrade. Clause ② mandates the contract-review tier (claude-fable-5), which is unavailable to this lane (maintainer 2026-08-20), so the card dispatches at opus and keeps needs:contract-review. Tier line from node scripts/pm/dispatch-gates.mjs --tier over the surface: "no path-derived mandate … Clause ② is NOT reachable from paths" — confirming the determination below is a content judgment, not a path lookup. ⚠️ That run came from a checkout 1 commit behind origin/main; the dev re-derives gate families in its own worktree.
    Clause-②: yes
    Serial constraints cleared: this claim TAKES the packages/rest/src/rest-server.ts fence for R39. It was released when #12280 merged (18:08Z) and no other claim holds it. The R39 siblings are #11624 (packages/cli/**, packages/lint/**) and #11719 (packages/types/src/response-envelope.ts, explicitly fenced out of rest-server.ts) — both disjoint. packages/client/src/index.ts was fenced behind #11925/PR #12062; that PR is merged onto main, so the fence is discharged. ⚠️ #11925 itself is still pm:dispatched and assigned to os-zhuang, a different account — ⛔ this seat does not touch that card's state; the file fence is discharged by the merge, independently of the card's residue. Surface does not intersect the #7898 H17 on-hold trigger-file index.

    Clause-② determination: yes, and the gate is hung in this same stroke (needs:contract-review applied with the claim, not deferred to review). The card asks for a refusal that does not exist today — SaveReportInput.query is required by the service contract and neither the SDK parameter nor the POST /reports route enforces it. Closing that narrows what the door accepts, which is a change to contract reject behaviour by the mechanical boundary test. ⛔ This seat's fuse bars it from clearing the gate: measured external_metadata.last_served_model is not CONTRACT_REVIEW_TIER. The verdict, when it comes, is recorded on this card, never on the PR.

    ⚠️ Do not "fix" the fixture. The card is explicit and it is the load-bearing point: packages/client/src/client.test.ts:352 constructs { name, object } with no query, and that fixture is evidence, not noise. It is a mock-transport test that never reaches a real service, so adding a query to it silences the signal without closing anything. Whatever shape the fix takes, the fixture's status must be reasoned about explicitly in the report.

    ⛔ packages/spec/src/contracts/report-service.ts is read-only here — it is the contract being honoured, not changed. Editing it moves the card to the domain:spec seat: stop and report.

    The six adjacent any-typed SDK methods listed in the card are out of scope — named there so a fix can consider whether the same reachable-contract argument applies, ⛔ not an invitation to sweep them. If any deserves its own card, file it unlabelled with a back-link rather than riding it in.


    Generated by Claude Code

  5. os-litant commented on Aug 26, 2026

    @os-litant
    Collaborator

    Dev claim — os-dev subagent, R39.

    Session: session_01UjujZN219uFzBhSYfMykCd (dev subagent dispatched by the domain:cli PM seat in this same session; the earlier claim comment 5419226591 is the PM half of this pair, not a second worker).
    Branch: claude/issue-11926-reports-save-input-contract — pushed empty at cdbd9204b before the first edit, as the write-route probe.
    Worktree: objectstack-issue-11926, off origin/main @ cdbd9204b65ae4ebec63f90f61669fb498efaa55.
    File surface accepted as claimed: packages/client/src/index.ts (reports.save), packages/rest/src/rest-server.ts (POST /reports), and their sibling tests. packages/spec/src/contracts/report-service.ts read-only. The six adjacent any-typed SDK methods out of scope.

    Anyone reading this as an unclaimed card: it is taken for R39. Report will follow on this card as a comment led by os-dev-report.


    Generated by Claude Code

  6. os-litant commented on Aug 26, 2026

    @os-litant
    Collaborator

    os-dev-report

    {
      "issue": 11926,
      "status": "done",
      "branch": "claude/issue-11926-reports-save-input-contract",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/12421",
      "premise_still_valid": true,
      "summary": "Premise holds and was reproduced before any fix. All three halves verified independently: the SDK parameter was any; the route forwarded req.body unchecked; and no report Zod schema exists at the door (0 hits, positive control MetadataTypeSchema 171 hits on the same instrument). FOUR PM/card figures falsified by measurement on cdbd9204b: client/src/index.ts 4185 to 4387, rest-server.ts 10293 to 10283, client.test.ts 352 to 378; only the spec contract line 89 was unmoved. The TS2345 reproduced with the identical message at the moved line 378. ONE card framing NARROWED by measurement, and reviewers should have it: the bundled implementation at packages/plugins/plugin-reports/src/report-service.ts:398 ALREADY refuses a query-less input, so no deployment on the bundled stack changes behaviour and the real blast radius is third-party IReportService implementations, not the default stack. The defect is still real and is exactly the three halves: the requirement was re-derived privately by each implementation instead of stated once at the seam. Fix is both halves the card recommended: reports.save bound to SaveReportInput, and POST /reports refusing missing name/object/query plus a non-object query with 400 VALIDATION_FAILED, ordered AFTER the existing 501 because an unmounted service is a deployment fact that outranks the body. The door refusal is RAISED THROUGH the existing handleValidation rather than writing its own body: the first design wrote one, which made a single route answer the same refusal in two different envelopes and added two non-conforming bodies to the check:route-envelope ratchet that only ticks down. FIXTURE DISPOSITION, as required: the query-less literal is neither given a query nor left alone. It is preserved VERBATIM in a new test where a ts-expect-error directive makes the compiler assert the refusal, which is a genuine bidirectional pin rather than a phantom because client.test.ts IS compiled by tsconfig.test.json, which the typecheck script names, and carries no debt-ledger entry. A SECOND fixture needed the same triage and would otherwise have gone silently vacuous: the REST pass-through test drove body {} and would have stayed green on the door instead of the service it exists to pin, so it is re-driven through a door-valid body and now asserts saveReport was actually called. OBSERVATION REPORTED BUT DELIBERATELY NOT FILED: packages/rest excludes all test files from its only tsc program, has no sibling test tsconfig and no TEST_DEBT entry, so its 147 test files appear to be type-checked by nothing; but check:type-check-coverage is green and its author reasoned explicitly about packages/rest, so my reading may be wrong and I did not file a card on an unconfirmed reading. PM to triage.",
      "tests": "All on final commit 9d4791c2d with a clean tree; the union was re-run AFTER the last commit. pnpm --filter @objectstack/rest test: 147 files / 2331 tests passed. pnpm --filter @objectstack/client test: 25 files / 347 tests passed. pnpm --filter @objectstack/client typecheck: EXIT 0, quoting the gate own verdict line, check:test-typecheck: OK, test layer compiles under packages/client/tsconfig.test.json, 0 file(s) / 0 error(s). pnpm --filter @objectstack/rest typecheck: EXIT 0 but SCOPED HONESTLY, packages/rest/tsconfig.json excludes all test files and tsc --listFiles returns 0 hits for rest.test.ts, so that green says NOTHING about the edited REST test file; that is acceptable here because the REST pins are runtime assertions and only the client pin is type-level, and that file IS in a program. pnpm lint over the whole repo (eslint . --no-inline-config): EXIT 0, full run, no narrowing claimed. Gate families re-derived in my own worktree via node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack over 5 changed paths at merge base cdbd9204b, NOT taken from the dispatch order. Green: route-envelope, dispatcher-error-vocabulary, authz-resolver, empty-changeset, changeset-gate-self-tests, objectui-changeset, published-files, page-declaration-shape, slot-lookup, test-source-alias, type-source-resolution, engine-double-contract, where-matcher, query-options-erasure, type-check-coverage, nul-bytes, skill-examples, comment-mask-adoption, adr-0087-registration, changeset-no-major, cross-package-test-inputs, plugin-teardown-shape, ci-filter-parity, affected-docs, and type-check-debt --re-measure (32 ledger entries re-measured, none above its recorded number). THREE non-green results, classified rather than reported as failures: skill-examples and type-check-debt both refused with PREREQUISITE NOT MET on an unbuilt closure, which reads as NOT MEASURED, and both were re-run green after building the prerequisite; route-envelope was a GENUINE red caused by my first design (stringError 46 vs declared 44, siblingCode 71 vs 69) and was closed by redesigning onto the existing construction site, never by raising the baseline. ABLATION, two legs. No rebuild was required, and the reason is stated rather than assumed: both tests import the mutated file through a package-local relative specifier (./index and ./rest-server), not through a dependency exports map, so no dist sits in the resolution path; the spec dist supplying SaveReportInput was built beforehand and neither leg touches it. Mutation confirmed on disk in each leg by grepping BOTH the injected and the removed text AND by comparing git hash-object against the HEAD blob (leg A 073f14e5 to 411c7db9, leg B 7b977048 to 418e9b9a). Leg A predicted RED with a DIFFERENT code than the original TS2345, and measured exactly that: TS2578 Unused ts-expect-error directive at client.test.ts:402, plus TS6196 for the now-unused import. Leg B predicted the two door pins RED and measured 2 failed / 226 passed (228), precisely those two, while the accepts-an-empty-query pin correctly stayed green because it asserts non-refusal. Restore was proved by git diff HEAD empty AND a byte-identical blob hash, never by an exit code, with the trap using absolute paths seeded from git rev-parse --show-toplevel. Leg B was RE-RUN against the final design after the route-envelope redesign, because the first run measured a superseded design. Exit codes were captured before any pipe throughout (redirect to file, then capture, then tail). One earlier leg-A attempt aborted on an unbound shell variable before tsc ran; that produced no reading and was discarded and re-run, not reported as a measurement.",
      "open_questions": [
        {
          "question": "The door refuses a query that is PRESENT but not a ReportQuery envelope (a scalar or an array), in addition to refusing the three missing required keys. That second check is slightly wider than a literal reading of required-key presence, and the contract review may want to rule on it.",
          "options": [
            "A: presence of name/object/query PLUS object-ness of query (implemented). Catches the authoring mistake that actually occurs, a query sent as a filter string, which would otherwise reach storage as a stringified scalar and be present enough to pass a pure presence check.",
            "B: presence only. Strictly the smallest statement of the contract required keys, leaving a scalar query to whatever the mounted implementation does with it."
          ],
          "recommendation": "A, because ReportQuery is an object type and a scalar is plainly not one, so refusing it states the declared contract rather than widening it; it is one condition, pinned in both directions (a refusal test for scalars and arrays, and an accepts-empty-query test so the rule cannot drift into refusing a legal empty envelope); and it is the AI-authored-metadata error class that the contract-first direction wants caught at the door rather than tolerated downstream. Reversing to B is a one-line deletion plus dropping one test if the review prefers the narrower statement."
        }
      ],
      "out_of_scope_findings": []
    }

    Generated by Claude Code


    Generated by Claude Code

  7. os-litant commented on Aug 26, 2026

    @os-litant
    Collaborator

    ✅ ACCEPT (substance) · ⛔ NOT enqueued — needs:contract-review stands

    PM review, domain:cli seat (#6024), R39, session session_01UjujZN219uFzBhSYfMykCd. PR #12421. Verdict on the card, ⛔ never on the PR.

    Checked against GitHub, ⛔ not against the report's self-account

    criterion reading
    PR shape draft ✓, base main ✓, Fixes #11926 first line ✓, 5 files / 2 commits
    declared surface honoured exactly — client/src/index.ts, rest/src/rest-server.ts, their two sibling tests, one changeset. ⛔ packages/spec/src/contracts/report-service.ts absent (ruling 3) · ⛔ the six adjacent any methods untouched (ruling 4)
    ACCEPT path fork get_files read directly: no governed surface. Irrelevant to arming here — the gate below binds first
    CI 29 runs, 0 failures, several in_progress at read time. ⛔ Not a release criterion for this card: it is gated regardless

    Clause ② — the gate stands, and the PR names its own reviewable decision

    ⛔ Not enqueued, not ready-flipped, not armed. This seat's fuse (measured last_served_model ≠ CONTRACT_REVIEW_TIER) bars it from clearing the gate. The PR body states the decision precisely enough for a reviewer to rule without re-deriving it:

    an IReportService implementation that treated query as optional and defaulted it can no longer receive a query-less body, even though the contract it implements has always declared query required.

    That is the accept/reject change, named rather than assumed away. The Clause-② yes scored at claim time was correct.

    ⭐ The correction a reviewer most needs — the card overstated its own blast radius

    The card says a query-less report is "refused, stored half-built, or throws depending entirely on which reports implementation is mounted." The dev measured that the bundled implementation already refuses (plugin-reports/src/report-service.ts:398), so:

    • ⛔ No deployment running the bundled reports service changes behaviour. What moves is the layer that produces the refusal — service → door.
    • The real runtime blast radius is third-party IReportService implementations, not the default stack.

    The defect is still real and is exactly the three measured halves — the requirement was re-derived privately by each implementation instead of stated once at the seam — but the severity framing on the card was wider than the tree. ⭐ Recorded here because the contract review should rule on the measured radius, not the card's.

    Four of the card's coordinates were falsified by measurement (index.ts 4185→4387, rest-server.ts 10293→10283, client.test.ts 352→378; only the spec contract line held). The TS2345 reproduction was obtained before any fix, at the moved line — so the defect was proven reachable rather than assumed.

    The fixture ruling was honoured, and then exceeded

    Ruling 1 said the query-less fixture is evidence and must not be silenced. It was neither silenced nor left alone: the literal is preserved verbatim under @ts-expect-error, so the compiler now asserts the refusal. The dev also proved that pin is not a phantom — client.test.ts is in tsconfig.test.json, carries no debt-ledger entry, and the ablation reds with TS2578 Unused '@ts-expect-error' directive. Evidence became a bidirectional pin.

    ⭐ And it found a second fixture that would have gone silently vacuous — the REST pass-through test drove body: {}, which the new door refuses before the service is consulted. Its assertions would have stayed green while pinning the wrong thing. It is re-driven through a door-valid body and now asserts saveReport was actually called. That is the false-green failure mode caught unprompted, on a test nobody asked about.

    ⭐ The check:route-envelope red was closed by redesign, ⛔ not by raising the ratchet. The first design wrote its own refusal body (stringError 46 vs 44, siblingCode 71 vs 69); it was rerouted through the existing handleValidation construction site instead, keeping the ratchet flat and the two 400s byte-identical. Raising a debt ledger's ceiling to absorb your own new errors is the gate-weakening floor, and this run did the opposite.

    Open question — PM ruling, ⛔ overturnable by the contract reviewer

    Asked: should the door refuse a query that is present but not an object (a scalar or array), on top of the three missing-key checks?

    Ruled A — as implemented. ReportQuery is an object type, so refusing a scalar states the declared contract rather than widening it; it is the authoring mistake that actually occurs (a query sent as a filter string, which otherwise reaches storage as a stringified scalar); and it is pinned in both directions — a refusal test for scalars and arrays, plus an "accepts an empty query object" test so the rule cannot drift from "present and an object" into "present and non-empty".

    ⚠️ Flagged explicitly to the contract reviewer as theirs to overturn. It is the widest part of the accept-set narrowing, and reversing to B is a one-line deletion plus dropping one test. ⛔ This seat's ruling does not bind that review.

    The observation the dev declined to file — filed as #12426

    The dev measured that packages/rest excludes every test file from its only tsc program (147 files, no sibling test tsconfig, no debt entry) and declined to file, because check:type-check-coverage is green and its author reasoned explicitly about that package — so the reading may be wrong. ⭐ Declining to file an unconfirmed reading was the right call.

    It is now filed as #12426, unlabelled, with its own refutation in the body and the single command that settles it. Filing it as prose in a report comment would have made it invisible to every candidate query, sweep and ageing alarm; filing it as a defect claim would have been a superstition with a citation attached. It is filed as neither.

    Deviations

    None. Every ruling honoured.

    Next

    ⛔ This card does not move further from this seat. It needs a contract review at the mandated tier; the verdict belongs on this card. The chain is live — #11925, #12047 and #11718 all cleared and merged through it today.


    Generated by Claude Code

  8. os-litant commented on Aug 27, 2026

    @os-litant
    Collaborator

    ⏳ Addendum — what the fence costs, ~26 h on

    domain:cli seat (#6024), R40. ⛔ Nothing new about the change itself; the R39 ACCEPT above stands unchanged and PR #12421 is untouched since 9d4791c2d. This records only the downstream cost, which was not on the card and is what should decide the review's priority.

    The PR holds five other cards, plus part of a sixth

    PR #12421's diff — re-derived from the PR itself this round, ⛔ not inherited — is five files:

    packages/client/src/index.ts
    packages/client/src/client.test.ts
    packages/rest/src/rest-server.ts
    packages/rest/src/rest.test.ts
    .changeset/reports-save-input-contract-at-the-door.md
    

    Under ruling ① (same-file hard serial, released by the merge, never by the arming), those two source files are what fence the rest of this lane's queue:

    card held via
    #12104 · #12034 · #12181 packages/client/src/index.ts
    #12510 packages/rest/src/rest-server.ts
    #12537 — its production half (fork (b)) packages/rest/src/rest-server.ts
    #12454 2 of its 3 sites
    #12573 — 4 of its 20 remaining ledger errors packages/rest/src/rest.test.ts

    ⚠️ The card→file mapping above is taken from each card's own stated target and is the weaker half of this reading; the authoritative half — PR #12421's file list — was re-derived from the PR's own diff. ⛔ Re-check the mapping before acting on any single row.

    Why this is a queue fact rather than a complaint

    ⭐ The PR is complete and verified. It is not waiting on work. Every ruling was honoured, both ablation legs measured, the check:route-envelope red closed by redesign rather than by raising the ratchet. It waits solely on a contract review at the mandated tier, which this seat's fuse (last_served_model ≠ CONTRACT_REVIEW_TIER, re-measured this round — unchanged) bars it from clearing.

    ⇒ the domain:cli execution queue is now dry for fence reasons, not for want of cards: with this PR landed, five cards become dispatchable immediately. That is the whole of the lane's remaining backlog apart from #12450 (waits on #11999), #12281 (contract-review) and the needs-user-decision set.

    ⛔ Not an argument for merging it unreviewed. The reviewable decision is real and is named precisely in the PR body — a third-party IReportService that treated query as optional can no longer receive a query-less body — and the PM ruling on the scalar/array refusal is flagged there as the reviewer's to overturn. This note only supplies the cost side, which the review could not otherwise see from the card.


    Generated by Claude Code

  9. os-litant commented on Aug 27, 2026

    @os-litant
    Collaborator

    Contract review: PASS — and the flagged seat ruling (A) is UPHELD (skills seat, session session_01MnijPVVDakqK2J335JoJtq; downgrade-fuse machine reading this sub-round: external_metadata.last_served_model = claude-fable-5).

    Reviewed PR #12421's contract increment against the tree, not the reports: ① packages/spec is untouched (zero diff lines) and the declared contract on origin/main reads query: ReportQuery — required, object-typed — so the door's refusal set (missing name/object/query; a present query that is a scalar or array) is exactly the declared accept-set enforced, not a narrowing beyond it: this is a declared=enforced restoration at the seam, which is the direction the contract-first rule exists to produce; ② the ruling flagged to this review — refuse a present-but-non-object query — is upheld: ReportQuery is an object interface, so a scalar was never inside the declared accept set, the check is pinned in both directions (scalar/array refused; the legal empty object accepted, so the rule cannot drift into "non-empty"), and it catches the AI-authoring error class (query sent as a filter string) at the door instead of downstream; ③ the refusal rides handleValidation's single VALIDATION_FAILED construction site, ordered after the 501 (deployment fact outranks body shape) — one envelope, ratchet flat; ④ the blast-radius correction is properly on record: the bundled implementation already refused, so what moves is the refusal's layer (service → door), and third-party IReportService implementations that defaulted a missing query were relying on behaviour the contract never granted. Clause-② declaration matched what the diff does.

    needs:contract-review cleared on this card in the same stroke. Per the standing clear-equals-land rule for un-governed code PRs, this chain now runs the pre-landing checks and takes the PR to the queue; the cli seat's substance-ACCEPT (2026-08-26 02:18) stands as the lane review.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions