Skip to content

feat: V2 parse & extract SDK support (client.v2)#105

Merged
yzld2002 merged 26 commits into
mainfrom
feat/v2-parse-extract
Jul 8, 2026
Merged

feat: V2 parse & extract SDK support (client.v2)#105
yzld2002 merged 26 commits into
mainfrom
feat/v2-parse-extract

Conversation

@yzld2002

@yzld2002 yzld2002 commented Jul 8, 2026

Copy link
Copy Markdown
Member

Implements Problem 2 — V2 parse/extract SDK support per the plan in #96 (tracked in #95). Additive client.v2 sub-client; V1 surface untouched.

Surface

  • client.v2.parse(...) — sync parse → V2ParseResponse (handles 206 partial-success; save_to; 504 → V2SyncTimeoutError)
  • client.v2.parse_jobs.{create,list,get,wait} — async parse jobs → normalized Job
  • client.v2.extract(...) — sync extract → V2ExtractResult (schema accepts a pydantic model / dict / JSON string; idempotency_key; options.strict)
  • client.v2.extract_jobs.{create,list,get,wait} — async extract jobs → Job
  • client.v2.files.upload(file) -> file_ref — stage large markdown for markdown_ref
  • wait() polling helper with backoff; one unified Job shape across the divergent parse/extract envelopes (.result typed, .error, .raw escape hatch)
  • Types under landingai_ade.types.v2

Hosts / environments

V2 routes to the AIDE gateway aide.[env].landing.ai (production aide.landing.ai, eu/staging/dev variants); V1 stays on api.va.[env].landing.ai. Selectable via environment= or LANDINGAI_ADE_ENVIRONMENT; v2_base_url/LANDINGAI_ADE_V2_BASE_URL overrides for mocks. (api.ade.* is the separate V1/tools VTRA host — not used for V2.)

Back-compat & testing

  • Additive only — no existing V1 method/type/signature/path changed; package stays 1.x.
  • Works under both pydantic v1 and v2; Python 3.9 floor.
  • 558 passed / 240 skipped (pydantic v2), 554 / 243 (pydantic v1 nox session); ./scripts/lint clean (ruff + pyright strict + mypy).

Notable bugs caught & fixed during the build

  • coerce_schema_to_dict used the pydantic-v2-only model_json_schema() → broke extract(schema=<pydantic model>) under pydantic v1. Fixed (v1 .schema()/definitions path).
  • pydantic-v1 model.schema() returns a memoized shared dict; in-place mutation corrupted the class cache and threw KeyError on the 2nd use of a nested schema. Fixed (deep-copy) + nested-model idempotency regression test.
  • Unset Omit/NotGiven sentinels leaked into the parse multipart form (multipart skips maybe_transform). Fixed (is_given filter) + regression test.

Follow-ups (non-blocking, from the final review)

  • parse_jobs.create() doesn't convert a local-path document_url the way sync parse() does (documented divergence).
  • V2ExtractResult requires extraction/markdown/metadata; a partial result on a completed extract job would raise during normalization — loosen if the gateway can return partials.
  • wait(raise_on_failure=True) keys off an attached error payload, not terminal failed/cancelled status alone.
  • Minor consistency: _build_extract_body treats explicit None like omit vs parse's is_given; list status typed Optional[str] not Literal.

Plan: #96 · Tracking: #95

🤖 Generated with Claude Code

yzld2002 and others added 17 commits July 8, 2026 13:32
Adds a V2_ENVIRONMENTS map and _resolve_v2_base_url() helper to LandingAIADE
and AsyncLandingAIADE, resolving self._v2_base_url alongside the existing V1
base_url. Precedence: explicit v2_base_url kwarg > LANDINGAI_ADE_V2_BASE_URL
env var > environment map > (if only V1 base_url was set) follow it >
production default. Also widens ENVIRONMENTS/environment Literal to include
staging and dev, and adds LANDINGAI_ADE_ENVIRONMENT env var support.

Foundation for the upcoming client.v2 sub-client (V2 parse/extract).
Hand-authored pydantic types for the V2 sub-client: JobStatus/JobError/Job
(one normalized job shape with is_terminal), V2ParseResponse+metadata+billing,
V2ExtractResult+metadata, and V2FileUploadResponse. All subclass the SDK's
_models.BaseModel for pydantic v1/v2 compat and permissive extra-field
retention.
test_parse_response_tolerates_loose_shape only exercised fields already
declared on the models, so it never proved the permissive extra="allow"
behavior. Add test_parse_response_retains_unknown_fields, which passes an
undeclared kwarg to each model and asserts it survives via .to_dict().
Adds normalize_parse_job and normalize_extract_job, collapsing the
divergent parse (epoch timestamps, failure_reason string) and extract
(ISO timestamps, error object) job envelopes into the unified Job type.
Add coerce_schema_to_dict (and helper pydantic_to_schema_dict) to
lib/schema_utils.py so v2.extract can take a BaseModel subclass, a
dict/Mapping, or a JSON string and normalize it to a plain dict for
the request body.
…iter

Adds V2ResourceMixin (_v2_url), poll_until_terminal/apoll_until_terminal
(fake-clock-testable backoff waiters), and JobList to resources/v2/_base.py;
wires an empty V2Resource/AsyncV2Resource container onto client.v2 via lazy
cached-property imports mirroring parse_jobs.

V2Resource intentionally has no sub-resource properties yet: pyright (strict)
resolves imports statically even inside a lazy-import function body, so
referencing the not-yet-created files.py/parse.py/extract.py modules (Tasks
7-11) would break `./scripts/lint` today. Each of those tasks adds its own
lazily-imported cached-property to v2.py once its backing module exists.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Wires a `files` cached_property (lazy import) into both V2Resource and
AsyncV2Resource containers, and unskips the Task 6 routing test now that
`.files` exists.
…dling

Adds ParseResource/AsyncParseResource (multipart POST to /v2/parse,
JSON-encoded `options` form field, 504 -> V2SyncTimeoutError via
raise_if_sync_timeout, save_to parity with V1 parse), and wires
client.v2.parse(...) / await client.v2.parse(...) into the V2
containers with an explicit typed signature.
Guard the is_given(...) filter in _build_parse_body with a respx test
asserting unset Omit/NotGiven sentinels never leak into the multipart
body; also refresh the stale V2Resource docstring now that files/parse
are wired up.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…ed Job

Adds ParseJobsResource/AsyncParseJobsResource to resources/v2/parse.py,
wired up as a lazy parse_jobs cached_property on both V2Resource and
AsyncV2Resource containers. create/get/list use cast_to=object to get
the raw envelope dict back from the transport and normalize it via
normalize_parse_job (Task 3); wait() polls to a terminal state using
poll_until_terminal/apoll_until_terminal (Task 6).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
- test_async_smoke.py: async parse/parse_jobs/extract smoke tests per the
  Task 12 brief, plus full create/get/wait coverage for
  AsyncExtractJobsResource (previously untested) using an injected
  monotonic clock so wait() doesn't sleep in real time.
- test_extract.py: add extract_jobs.get("") empty-job_id ValueError test
  and extract_jobs.list() envelope test (has_more/page/page_size +
  normalized Job items), mirroring existing parse_jobs coverage.

Full suite green: rye run pytest -q -> 557 passed, 240 skipped.
./scripts/lint clean (ruff + pyright strict + mypy + import check).

Note: rye run nox -s test-pydantic-v1 fails on 2 pre-existing tests
(test_extract_sync_json_body_with_pydantic_schema,
test_coerce_from_pydantic_model) due to a real bug in
schema_utils.coerce_schema_to_dict, which gates on the pydantic-v2-only
model_json_schema attribute and raises TypeError for valid pydantic v1
BaseModel subclasses. Not fixed here (tests-only task); see
.superpowers/sdd/task-12-report.md for details.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
pydantic_to_json_schema/pydantic_to_schema_dict gated on
hasattr(model, "model_json_schema") and read $defs, both v2-only. Under
pydantic v1 a genuine BaseModel subclass lacks model_json_schema (v1 uses
.schema() + a "definitions" key instead), so coerce_schema_to_dict raised
TypeError, breaking client.v2.extract(schema=<v1 model>).

Add _model_json_schema() which dispatches on the SDK's existing PYDANTIC_V1
flag: v1 calls .schema() and renames "definitions" to "$defs"; v2 is
unchanged. Replace the hasattr guard with a proper
isinstance(model, type) and issubclass(model, BaseModel) check that works
on both majors.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
pydantic v1's model.schema() is memoized per class and returns the same
shared mutable dict on every call. schema_utils mutated that dict in
place (popping "definitions"/"$defs"), which permanently corrupted the
class-level cache: a second call on the same model with nested
sub-models raised KeyError in _resolve_refs due to a dangling $ref.

Deep-copy the schema in _model_json_schema before any mutation, for
both pydantic majors, so repeated calls on the same class are safe.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Add a V2 section to api.md (parse/parse_jobs/extract/extract_jobs/files
methods and the unified Job/JobStatus/JobError/V2ParseResponse/
V2ExtractResult/V2FileUploadResponse types), a V2 API subsection to
README.md (environment selection incl. LANDINGAI_ADE_ENVIRONMENT, sync
parse/extract, async jobs + wait, pydantic-model schema, files.upload ->
markdown_ref, save_to), and runnable examples/v2_parse.py and
examples/v2_extract.py. Additive docs only; V1 usage is unchanged.
Copilot AI review requested due to automatic review settings July 8, 2026 08:33
examples/v2_smoke_test.py exercises files.upload, parse (sync+job), extract
(sync+job) against a live environment (default staging) with per-check PASS/FAIL
reporting and --only/--async/--document selection. Manual QA tool, not part of the
automated suite.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Adds an additive client.v2 sub-client that targets the ADE gateway host (aide.[env].landing.ai) to support V2 parse/extract (sync + async jobs), unified job normalization + polling helpers, and accompanying types, docs, examples, and tests—without changing the existing V1 surface.

Changes:

  • Introduces client.v2 resources for sync parse/extract, async job routes, file staging (/v1/files), and a shared wait() polling helper.
  • Adds V2 type models and job envelope normalizers (parse vs extract) plus V2-specific error wrappers (notably sync 504 → V2SyncTimeoutError).
  • Expands environment handling to include staging/dev and adds docs/examples/tests for the V2 surface.

Reviewed changes

Copilot reviewed 31 out of 32 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/test_v2_waiter.py Unit tests for sync/async polling backoff/timeout/failure behavior and JobList envelope handling.
tests/test_v2_types.py Unit tests for V2 type models (Job, V2ParseResponse, V2ExtractResult, upload response).
tests/test_v2_schema.py Regression and compatibility tests for schema coercion across pydantic v1/v2.
tests/test_v2_normalize.py Tests for parse/extract job envelope normalization (epoch vs ISO timestamps, result/error mapping).
tests/test_v2_errors.py Tests for V2 error classes and 504 conversion helper.
tests/test_v2_environment.py Tests for V2 host/environment resolution and v2 subclient routing.
tests/api_resources/v2/test_parse.py API-style tests for V2 parse sync + parse jobs routes and async parity.
tests/api_resources/v2/test_files.py API-style tests for file staging upload routing/auth and async parity.
tests/api_resources/v2/test_extract.py API-style tests for V2 extract sync + extract jobs routes, strict/idempotency, and 504 handling.
tests/api_resources/v2/test_async_smoke.py Async smoke coverage for v2 parse/extract + jobs (create/get/wait).
tests/api_resources/v2/init.py Marks v2 API resource tests package.
src/landingai_ade/types/v2/parse_response.py Defines permissive V2 parse response + metadata/billing types.
src/landingai_ade/types/v2/job.py Defines unified V2 Job shape, status enum, and error payload.
src/landingai_ade/types/v2/file_upload_response.py Defines upload response type carrying file_ref.
src/landingai_ade/types/v2/extract_response.py Defines typed V2 extract result + metadata types.
src/landingai_ade/types/v2/init.py Re-exports V2 types under landingai_ade.types.v2.
src/landingai_ade/resources/v2/v2.py Adds V2Resource / AsyncV2Resource container exposing client.v2.* surface.
src/landingai_ade/resources/v2/parse.py Implements V2 parse sync + parse jobs create/get/list/wait (and async equivalents).
src/landingai_ade/resources/v2/files.py Implements ADE-host file staging upload (/v1/files) with sync/async variants.
src/landingai_ade/resources/v2/extract.py Implements V2 extract sync + extract jobs create/get/list/wait (and async equivalents).
src/landingai_ade/resources/v2/_normalize.py Normalizes divergent parse/extract job envelopes into unified Job.
src/landingai_ade/resources/v2/_base.py Provides V2 URL helper, polling/backoff waiter, and JobList envelope container.
src/landingai_ade/resources/v2/init.py Exposes V2 resource container classes.
src/landingai_ade/lib/v2_errors.py Adds V2-specific error types and sync-timeout conversion helper.
src/landingai_ade/lib/schema_utils.py Adds pydantic v1/v2 schema extraction normalization and coerce_schema_to_dict.
src/landingai_ade/lib/init.py Re-exports coerce_schema_to_dict in the public lib package.
src/landingai_ade/_client.py Adds V2 environment map + V2 base URL resolution and client.v2 cached property.
README.md Documents V2 API usage, environment selection, and examples for sync/jobs/upload flows.
examples/v2_parse.py Runnable example using V2 parse jobs + sync parse.
examples/v2_extract.py Runnable example using V2 extract (pydantic schema) + extract jobs.
api.md Adds V2 section documenting client.v2 methods, types, and semantics.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/landingai_ade/resources/v2/_base.py
Comment thread src/landingai_ade/resources/v2/_normalize.py Outdated
Copilot AI review requested due to automatic review settings July 8, 2026 08:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 31 out of 32 changed files in this pull request and generated 6 comments.

Comment thread src/landingai_ade/resources/v2/parse.py
Comment thread src/landingai_ade/resources/v2/parse.py
Comment thread src/landingai_ade/resources/v2/parse.py
Comment thread src/landingai_ade/resources/v2/extract.py
Comment thread src/landingai_ade/resources/v2/extract.py
Comment thread examples/v2_smoke_test.py Outdated
JobList.build coerced has_more via bool(), so a wrong-typed truthy
string (e.g. "false") flipped pagination; now only a real bool sets
it, matching the isinstance checks used for the sibling fields.

normalize_parse_job used `created_at or received_at`, which treated
a valid epoch-0 created_at as falsy and silently fell back to
received_at; now falls back only when created_at is None.
Copilot AI review requested due to automatic review settings July 8, 2026 08:43
@yzld2002 yzld2002 added the breaking-change-approved Surface-lock override: a reviewed, intentional public-surface change label Jul 8, 2026
@yzld2002

yzld2002 commented Jul 8, 2026

Copy link
Copy Markdown
Member Author

surface-lock: applied the breaking-change-approved override (additive, not a real break)

CI's surface-lock (griffe vs the last release tag) flagged one item:

src/landingai_ade/_client.py:88: ENVIRONMENTS: Attribute value was changed:
  {production, eu} -> {production, eu, staging, dev}

This is the V1/V2 environment map gaining staging and devpurely additive (new keys on a public constant), not a breaking change to any signature or behavior. Notably the environment Literal widening did NOT trip griffe (it correctly treats input-type widening as compatible); only the constant's value did. The whole-branch review independently confirmed the branch is additive-only.

I applied the gate's breaking-change-approved label to override, since this is a reviewed, intentional public-surface change. (The label name is broader than this case — it's the gate's generic surface-lock override.)

Follow-up for the spec-sync pipeline (Problem 3): surface-lock currently treats a public constant value change as a breakage. Adding an environment shouldn't require an override. Worth refining the gate to ignore attribute-value-only changes (or renaming the override label to surface-change-approved). Tracking separately.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 31 out of 32 changed files in this pull request and generated 1 comment.

Comment thread src/landingai_ade/lib/v2_errors.py
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
Copilot AI review requested due to automatic review settings July 8, 2026 10:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 31 out of 32 changed files in this pull request and generated 1 comment.

Comment thread src/landingai_ade/types/v2/job.py Outdated
Comment on lines +33 to +37
# Populated on completion: V2ParseResponse for parse jobs, V2ExtractResult for extract jobs.
result: Optional[object] = None
error: Optional[JobError] = None
# Full original envelope for fields not surfaced above (org_id, output_url, version, ...).
raw: Dict[str, object] = {}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 6950460: Job.raw now uses Field(default_factory=dict) (with the Field import) so each instance gets its own dict. Added a test that mutates one Job's raw and asserts another Job's raw is unaffected; verified under both pydantic v1 and v2.

…ted)

A live staging smoke test with a real customer bearer key confirmed
api.ade.staging.landing.ai serves files.upload/extract/extract_jobs
successfully, while aide.staging.landing.ai fails all calls with a
Google-frontend 400 (the host is gated behind staff Google SSO).
aide.[env] remains the spec-publication host only, not the callable
customer API.

Update V2_ENVIRONMENTS in src/landingai_ade/_client.py and all
respx-mocked/asserted hosts in tests, plus docs/examples referencing
the V2 host, from aide.[env].landing.ai to api.ade.[env].landing.ai.
V1 host (api.va.[env].landing.ai) is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Copilot AI review requested due to automatic review settings July 8, 2026 13:22
…(live-confirmed)

The live /v2/parse/jobs create (202) response is minimal ({"job_id": "..."} only,
no status field), causing normalize_parse_job to KeyError on raw["status"].
normalize_extract_job has the same latent issue. Default to JobStatus.PENDING
when status is absent/empty, since a just-created job is pending.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 31 out of 32 changed files in this pull request and generated 2 comments.

Comment thread src/landingai_ade/types/v2/job.py
Comment on lines +35 to +38
error: Optional[JobError] = None
# Full original envelope for fields not surfaced above (org_id, output_url, version, ...).
raw: Dict[str, object] = {}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 6950460: Job.raw now uses Field(default_factory=dict) (with the Field import) so each instance gets its own dict. Added a test that mutates one Job's raw and asserts another Job's raw is unaffected; verified under both pydantic v1 and v2.

Copilot AI review requested due to automatic review settings July 8, 2026 13:27

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 31 out of 32 changed files in this pull request and generated 1 comment.

Comment thread src/landingai_ade/types/v2/job.py Outdated
Comment on lines +24 to +37

class Job(BaseModel):
"""One normalized job shape across parse and extract (envelopes diverge upstream)."""

job_id: str
status: JobStatus
created_at: Optional[datetime] = None
completed_at: Optional[datetime] = None
progress: Optional[float] = None
# Populated on completion: V2ParseResponse for parse jobs, V2ExtractResult for extract jobs.
result: Optional[object] = None
error: Optional[JobError] = None
# Full original envelope for fields not surfaced above (org_id, output_url, version, ...).
raw: Dict[str, object] = {}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch — writing the construct-path test exposed a real quirk: Job.raw uses Field(default_factory=dict) (from 6950460), but the SDK's construct() fast path called field_get_default() without call_default_factory=True, so on pydantic v2 raw resolved to None rather than a fresh dict. Fixed in 3890816 (_compat.field_get_default now passes call_default_factory=True on v2, matching pydantic's guidance). Added a Job.construct()-path test asserting per-instance independent raw. (Our normalizers build Job via the validating constructor, but this makes the construct path correct too.)

- V2SyncTimeoutError message now points at the real async helpers
  (client.v2.parse_jobs / client.v2.extract_jobs) instead of the
  nonexistent .jobs attribute.
- Job.raw now uses Field(default_factory=dict) instead of a mutable
  {} default so instances don't share the same dict.
- Add regression test asserting Job.raw is independent per instance.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Copilot AI review requested due to automatic review settings July 8, 2026 13:35

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 31 out of 32 changed files in this pull request and generated 2 comments.



def normalize_parse_job(raw: Mapping[str, Any]) -> Job:
status = JobStatus(raw.get("status") or "pending")

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 3890816: added a tolerant _status() helper — an unknown/renamed status string from the gateway now falls back to JobStatus.PENDING instead of raising ValueError and crashing the normalizer; the original status stays available via job.raw. Regression test added (a brand-new status value → PENDING, raw preserved), confirmed to fail without the fix, under both pydantic majors.



def normalize_extract_job(raw: Mapping[str, Any]) -> Job:
status = JobStatus(raw.get("status") or "pending")

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed in 3890816: added a tolerant _status() helper — an unknown/renamed status string from the gateway now falls back to JobStatus.PENDING instead of raising ValueError and crashing the normalizer; the original status stays available via job.raw. Regression test added (a brand-new status value → PENDING, raw preserved), confirmed to fail without the fix, under both pydantic majors.

…ependence (review)

- _normalize.py: unknown/renamed gateway status strings no longer raise
  ValueError and crash the whole normalizer; they fall back to PENDING
  while the original raw status stays available via job.raw.
- _compat.py: field_get_default() now passes call_default_factory=True on
  pydantic v2, matching pydantic's own guidance for the construct()/
  model_construct() fast path. Without it, fields declared with
  default_factory=... (e.g. Job.raw) resolved to None instead of a fresh
  value when built via Job.construct()/model_construct(), which the new
  test exposed.
- Tests: unknown-status coverage for both normalizers, and a
  construct()-path test proving Job.raw stays independent per instance.
Copilot AI review requested due to automatic review settings July 8, 2026 15:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 32 out of 33 changed files in this pull request and generated 2 comments.

Comment thread src/landingai_ade/_client.py
Comment thread src/landingai_ade/types/v2/file_upload_response.py
@yzld2002 yzld2002 enabled auto-merge (squash) July 8, 2026 15:36
@yzld2002 yzld2002 disabled auto-merge July 8, 2026 15:36
@yzld2002 yzld2002 merged commit 50682fe into main Jul 8, 2026
6 checks passed
@yzld2002 yzld2002 deleted the feat/v2-parse-extract branch July 8, 2026 15:36
yzld2002 added a commit that referenced this pull request Jul 13, 2026
…hanges

griffe flags a changed value of a public constant (e.g. adding staging/dev to the
ENVIRONMENTS map) as breaking, tripping the gate on every PR since #105. Additive
config is not an API-signature break, so filter griffe's 'Attribute value was changed'
breakages; any other breakage still fails the gate (verified: renaming a public method
still trips it). Removes the need for the breaking-change-approved override on such PRs.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking-change-approved Surface-lock override: a reviewed, intentional public-surface change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants