Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
113 commits
Select commit Hold shift + click to select a range
16cd5bc
bump version 1.22.3
wvandeun Aug 19, 2026
dbf6986
add release notes
wvandeun Aug 19, 2026
db6e0c7
Merge pull request #1255 from opsmill/prep-release-1.22.3
wvandeun Aug 19, 2026
8300e68
Add watch for generators
ogenstad Jun 25, 2026
10a45a0
ci: dispatch infrahub-sdk updates to infrahub-sync
Jul 2, 2026
7fce489
Add missing protocols for Infrahub 1.11 (#1134)
ogenstad Jul 3, 2026
9a85580
add MERGE_FAILED to the BranchStatus enum (#1123)
ajtmccarty Jul 8, 2026
169f1cb
add the "ordered" schema attribute property (#1150)
ajtmccarty Jul 9, 2026
08fa009
fix(ctl): import pyarrow lazily in the JSON importer
Jul 10, 2026
645553a
spec(ihs-249): SDK retry with backoff on HTTP 429 responses
dgarros Jul 7, 2026
97886fd
plan(ihs-249): design SDK 429 retry with backoff
dgarros Jul 7, 2026
f821605
critique(ihs-249): dual-lens review + apply must-address fixes
dgarros Jul 7, 2026
54d96aa
tasks(ihs-249): dependency-ordered task breakdown
dgarros Jul 7, 2026
51cd2c5
align(ihs-249): spec/PRD alignment check — ALIGNED
dgarros Jul 7, 2026
1fe0ccd
plan(ihs-249): make async+sync dual coverage explicit
dgarros Jul 7, 2026
26ec3ec
feat(client): add HTTP 429 retry with backoff (handler, config, drivers)
dgarros Jul 7, 2026
ef6bbc5
test: add client-level 429 retry tests for _request path (US1)
dgarros Jul 7, 2026
ce019bd
test(sdk): add US2 Retry-After client tests for 429 retry driver
dgarros Jul 7, 2026
112a7c5
test(rate-limit): client-level 429 retry exhaustion tests (US3)
dgarros Jul 7, 2026
28a33ad
test(sdk): add US4 disable/tune/parity 429 retry tests (T016)
dgarros Jul 7, 2026
d417166
test: cover all request paths and multipart body re-read for 429 retry
dgarros Jul 7, 2026
bd93619
fix: floor negative and guard overflow in Retry-After delta parsing
dgarros Jul 7, 2026
ab49028
docs(ihs-249): opsmill implement report — DONE
dgarros Jul 7, 2026
fdfbe23
ci: fix markdownlint and vale errors in ihs-249 spec docs
dgarros Jul 8, 2026
c40ab56
fix(client): harden 429 retry exhaustion, streaming cleanup, and backoff
dgarros Jul 9, 2026
d4125e1
chore: stop tracking .specify/feature.json
dgarros Jul 10, 2026
f1d46e3
refactor(client): encapsulate 429 retry in RateLimitRetryHandler; tri…
dgarros Jul 10, 2026
f828cb1
chore(changelog): drop 1124.changed.md, keep a single fragment
dgarros Jul 10, 2026
c401806
feat(ctl): add deployment_id to the info command
dgarros Jul 11, 2026
9c74bfa
chore(changelog): add fragment for deployment_id info feature
dgarros Jul 11, 2026
fefd01c
refactor(client): combine version and deployment_id into get_server_i…
dgarros Jul 12, 2026
6fe1bf1
feat(release): derive package version from git tags via hatch-vcs
fatih-acar Jul 10, 2026
07eb001
docs(specs): add spec for SDK X-Priority request header (IHS-259)
dgarros Jul 10, 2026
bb88b2d
docs(specs): add implementation plan for X-Priority header (IHS-259)
dgarros Jul 10, 2026
62e6be2
docs(specs): critique X-Priority spec/plan and apply refinements (IHS…
dgarros Jul 10, 2026
72540fd
docs(specs): add dependency-ordered tasks for X-Priority header (IHS-…
dgarros Jul 10, 2026
5a0176e
docs(specs): add spec/ask alignment check for X-Priority (IHS-259)
dgarros Jul 10, 2026
2506cb6
chore(specs): mark T001 setup done (baseline 107 passed) [IHS-259]
dgarros Jul 11, 2026
e9e85ca
feat(sdk): add Priority enum and Config.priority field [IHS-259]
dgarros Jul 11, 2026
40d7ae6
feat(sdk): emit X-Priority default header across transports [IHS-259]
dgarros Jul 11, 2026
3a490da
test(sdk): assert no X-Priority header when unconfigured [IHS-259]
dgarros Jul 11, 2026
fead632
feat(sdk): add per-request priority override across client + node [IH…
dgarros Jul 11, 2026
43990f3
test(sdk): validate Config.priority accepts case-insensitive, rejects…
dgarros Jul 11, 2026
a6b7d1d
test(sdk): assert async/sync parity via priority resolution truth tab…
dgarros Jul 11, 2026
89e848a
docs(sdk): docstrings, regenerated docs, and changelog for X-Priority…
dgarros Jul 11, 2026
f0338b8
fix(client): keep live auth header on relogin retry after priority me…
dgarros Jul 11, 2026
e0b4725
test(sdk): cover node delete/update and all diff methods for priority…
dgarros Jul 11, 2026
02b4da5
docs(specs): add opsmill implement report for X-Priority [IHS-259]
dgarros Jul 11, 2026
f736cc6
docs(sdk): regenerate SDK reference docs after rebase onto develop [I…
dgarros Jul 12, 2026
d3c2493
docs(specs): fix markdown lint in X-Priority critique doc [IHS-259]
dgarros Jul 12, 2026
3b964a8
chore: stop tracking .specify/feature.json [IHS-259]
dgarros Jul 12, 2026
7e99f52
feat(sdk): thread priority through count and resource-pool peer fetch…
dgarros Jul 12, 2026
f7eb7ea
docs(sdk): shrink changelog, fix contract/data-model, regenerate docs…
dgarros Jul 12, 2026
ef694f5
test(sdk): drop ty-ignore in Config.priority tests via dict[str, Any]…
dgarros Jul 12, 2026
cd5a2c3
refactor(client): layer per-request header deltas over base headers […
dgarros Jul 14, 2026
ab60207
chore(sdk): apply review nits — Priority import, docstrings, test cle…
dgarros Jul 14, 2026
48b9fd6
docs(sdk): regenerate SDK reference after rebase onto infrahub-develo…
dgarros Jul 14, 2026
2952d5a
feat(sdk): add task retry/cancel methods and expose available actions…
polmichel Jul 16, 2026
01f7c6f
chore(ruff): adopt google docstring convention, enforce DOC102/DOC402
dgarros Jul 15, 2026
7ee6566
chore(ruff): trim redundant comments around docstring convention config
dgarros Jul 16, 2026
fbcb569
feat(sdk): expose task error and webhook delivery diagnostics (#1197)
polmichel Jul 21, 2026
fd7f55b
feat: add IPAddress attribute kind support [INFP-551]
PhillSimonds Jul 19, 2026
3596555
perf(client): pass pagination offset and limit as GraphQL variables
Jul 23, 2026
816d834
feat(client): carry request priority on RequestContext
dgarros Jul 16, 2026
ba6f582
refactor(priority): rename the middle tier NORMAL to MEDIUM
dgarros Jul 16, 2026
b0b5bce
feat(config): raise default rate_limit_max_retries from 5 to 10
Jul 24, 2026
c9e8b5d
feat(schema): generate user-facing write/read schema models [INFP-234]
dgarros Jul 3, 2026
e281381
feat(schema): offline write-contract validation + generated-model dri…
dgarros Jul 3, 2026
c121d45
fix(schema): gate schema extensions through the write contract
dgarros Jul 3, 2026
632e542
refactor(schema): name generated models InfrahubSchema{Write,Read} + …
dgarros Jul 5, 2026
37f5847
refactor(schema): validate against InfrahubSchemaWrite root instead o…
dgarros Jul 5, 2026
cc72f69
feat(schema): generate a typed ComputedAttribute model instead of dic…
dgarros Jul 5, 2026
30ddc7f
feat(schema): emit typed models for choices, parameters, and computed…
dgarros Jul 5, 2026
0db7361
feat(schema): model generated attribute as a kind-discriminated union
dgarros Jul 5, 2026
f728151
feat(schema): add extensions to write model and profile/template read…
dgarros Jul 6, 2026
231a285
feat(schema): type generated SDK schema fields with dedicated enums
dgarros Jul 6, 2026
ea27bcf
refactor(schema): back public SDK schema models with generated models…
dgarros Jul 6, 2026
44d882c
fix(schema): render generated enum-typed defaults as enum members
dgarros Jul 6, 2026
9161e24
test(schema): suppress expected ty error for raw-string enum coercion…
dgarros Jul 7, 2026
ea11136
refactor(schema): move kind/hash onto generated read models [INFP-234]
dgarros Jul 7, 2026
b951dc7
feat(schema): expose kind as a property on write schema nodes [INFP-234]
dgarros Jul 8, 2026
b1dcb35
chore(schema): regenerate SDK models and protocols after develop reba…
dgarros Jul 14, 2026
12af188
fix(schema): normalize schema.load/check payloads to the write contra…
dgarros Jul 16, 2026
1face70
Revert "normalize schema.load/check payloads to the write contract" […
dgarros Jul 16, 2026
165ea52
refactor(schema): address PR review on SDK schema models and tests [I…
dgarros Jul 22, 2026
d70aaa1
refactor(schema): tolerate and drop extra fields on write/read models…
dgarros Jul 22, 2026
48359e9
docs(changelog): drop breaking label from SDK schema-models note [INF…
dgarros Jul 22, 2026
438f1cd
test(schema): skip IPAddress node tests pending backend attribute kin…
dgarros Jul 23, 2026
07bc7ba
fix(schema): require version on the generated write root [INFP-234]
dgarros Jul 24, 2026
7779004
chore(review): clarify schema model comments [INFP-234]
dgarros Jul 26, 2026
72fc224
feat(ctl): add `infrahubctl schema format` command
petercrocker Jul 19, 2026
c59cf87
refactor(ctl): use ruamel.yaml so schema format preserves comments
petercrocker Jul 19, 2026
d0c3a32
ci(ctl): warn-only check for Infrahub JSON schema drift
petercrocker Jul 20, 2026
75a5b2c
fix(ctl): harden schema formatter against malformed sections and head…
petercrocker Jul 20, 2026
1ea829b
style(ctl): add blank line after last docstring section (ruff D413)
petercrocker Jul 20, 2026
c0cac67
docs: regenerate infrahubctl schema reference after develop merge
petercrocker Jul 20, 2026
e6f65a3
fix(ctl): colour schema-format --diff via Rich style, not inline markup
petercrocker Jul 20, 2026
45f367e
feat(ctl): add opt-in strip-defaults / sort / backfill flags to schem…
petercrocker Jul 21, 2026
99e923a
refactor(ctl): return an enum from schema-format helper; harden sort …
petercrocker Jul 29, 2026
8f4de3c
test(ctl): cover schema-format error paths and drift fetch/baseline IO
petercrocker Jul 29, 2026
7d0c1b0
fix(ctl): treat unparseable schema files as a per-file format error
petercrocker Jul 29, 2026
4ea2bbf
feat(schema): add IPAddress to the generated attribute-kind models (#…
ajtmccarty Jul 31, 2026
78060c6
feat: report the schema fields the write contract does not apply
dgarros Jul 31, 2026
5fabbec
fix: name a nested read-only field relative to its owner
dgarros Jul 31, 2026
9474ffe
refactor: look the read-only table up by class name alone
dgarros Aug 2, 2026
a592f7c
Remove broken client.branch.diff_data() (#1229)
saltas888 Aug 11, 2026
22fc7ac
feat: warn when a watch block is missing or incomplete in .infrahub.y…
ogenstad Aug 18, 2026
c7313d5
add release notes
wvandeun Aug 19, 2026
eabb609
fix
wvandeun Aug 19, 2026
99a380a
Merge pull request #1259 from opsmill/prep-release-1.23.0
wvandeun Aug 19, 2026
7cf2424
chore(deps-dev): bump infrahub-testcontainers from 1.10.6 to 1.10.8
dependabot[bot] Aug 19, 2026
d0537e5
Merge pull request #1252 from opsmill/dependabot/uv/stable/infrahub-t…
ajtmccarty Aug 19, 2026
f210dde
Merge branch 'stable' into 'develop' with resolved conflicts
ogenstad Aug 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ dist/*

# Generated files
generated/
# Committed, generated schema models (write/read variants) must be version-controlled.
!infrahub_sdk/schema/generated/
!infrahub_sdk/schema/generated/*.py
sandbox/

# hatch-vcs version file (written at build time; must not be tracked)
Expand Down
1 change: 1 addition & 0 deletions .vale/styles/Infrahub/sentence-case.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ exceptions:
- IP
- IP Fabric
- IPAM
- IPAddress

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.

P3: The new IPAddress exception breaks the alphabetical ordering of the list. Case-insensitively, ipaddress sorts before ipam (the d in IPAddress precedes the m in IPAM), but it is inserted after IPAM. Move it above IPAM to match the list's ordering convention.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At .vale/styles/Infrahub/sentence-case.yml, line 48:

<comment>The new `IPAddress` exception breaks the alphabetical ordering of the list. Case-insensitively, `ipaddress` sorts before `ipam` (the `d` in IPAddress precedes the `m` in IPAM), but it is inserted after `IPAM`. Move it above `IPAM` to match the list's ordering convention.</comment>

<file context>
@@ -45,6 +45,7 @@ exceptions:
   - IP
   - IP Fabric
   - IPAM
+  - IPAddress
   - IPHost
   - IPNetwork
</file context>

- IPHost
- IPNetwork
- JavaScript
Expand Down
38 changes: 38 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,44 @@ This project uses [*towncrier*](https://towncrier.readthedocs.io/) and the chang

<!-- towncrier release notes start -->

## [1.23.0](https://github.com/opsmill/infrahub-sdk-python/tree/v1.23.0) - 2026-08-19

### Removed

- Removed `client.branch.diff_data()` from both the async and sync clients. The method relied on a `GET /api/diff/data` REST endpoint that does not exist in Infrahub, so every call returned a 404. Use `client.get_diff_tree()` to retrieve the full diff of a branch against its base branch, or `client.get_diff_summary()` for the list of changed nodes; both use the `DiffTree` GraphQL query. ([#325](https://github.com/opsmill/infrahub-sdk-python/issues/325))

### Added

- Added the Infrahub deployment ID to the `infrahubctl info` command output and a `get_server_information()` method (returning the server version and deployment ID) on the async and sync clients. ([#1017](https://github.com/opsmill/infrahub-sdk-python/issues/1017))
- Added transparent retry of HTTP 429 (rate-limited) responses on both `InfrahubClient` and `InfrahubClientSync`. Retries use jittered exponential backoff and honour a server-provided `Retry-After` header (delta-seconds or HTTP-date). The behaviour is tunable through four new `Config` fields (`rate_limit_retry_enabled`, `rate_limit_max_retries`, `rate_limit_backoff_base`, `rate_limit_backoff_max`), and a new `RateLimitError` exception is raised when retries are exhausted. ([#1124](https://github.com/opsmill/infrahub-sdk-python/issues/1124))
- Added support for tagging SDK requests with a priority via a new `X-Priority` header. A `Priority` enum (`high`, `medium`, `low`) is available from `infrahub_sdk.constants`; set `Config.priority` (env var `INFRAHUB_PRIORITY`) for a client-wide default emitted on every request, or pass `priority=` to individual operations to override it per request. When unset, no header is sent. Works identically on `InfrahubClient` and `InfrahubClientSync`. ([#1151](https://github.com/opsmill/infrahub-sdk-python/issues/1151))
- Add `infrahubctl schema format` command, an opinionated offline formatter that normalises the key ordering of schema files. Optional flags can also strip redundant default values (`--strip-defaults`), sort attributes/relationships by `order_weight` (`--sort-by-order-weight`), and backfill a missing `order_weight` (`--backfill-order-weight`).
- Added `retry()` and `cancel()` methods to the task manager. The `Task` model now exposes `available_actions` along with `can_retry` / `can_cancel` helpers.
- Added an opt-in `include_diagnostics` flag to the task manager's `all()`, `filter()`, and `get()` methods. When enabled, tasks expose an `error` field, and `webhook-send` tasks are returned as `WebhookDeliveryTask` instances carrying `http_request` / `http_response` delivery details.
- Added support for the new `IPAddress` attribute kind. Values are exposed as bare `ipaddress.IPv4Address`/`IPv6Address` objects (no prefix) and serialized to a bare-address string when writing, alongside the existing `IPHost` and `IPNetwork` kinds.
- The JSON schema generated for `.infrahub.yml` now warns when a definition has not said what it depends on, so YAML language servers flag it while the file is being edited. A Python transform or generator definition with no `watch` block is flagged, and so is a `watch` value that is not a mapping, including the bare `watch:` that parses as null and records nothing. Both warnings are advisory only: the models still accept every one of those forms. An empty `watch: {}` or `files: []` stays clean, since either one records that the author checked and nothing beyond what Infrahub detects needs watching. The generated schema also picks up the `watch` block on generator definitions, which it was previously rejecting as an unknown property.
- The request priority (`X-Priority` header) can now be carried on the client's `RequestContext` via a new `priority` field, alongside the existing client-wide `Config.priority` default and per-call `priority=` override. Resolution precedence is per-call `priority=` > `request_context.priority` > `Config.priority` > no header. The priority is emitted as a header only and is never included in the mutation body. Works identically on `InfrahubClient` and `InfrahubClientSync`.
- Import `pyarrow` lazily in the line-delimited JSON importer so that `infrahubctl` commands other than `object load` no longer require the `ctl` extra (and its heavy `pyarrow` dependency) to be installed.

### Changed

- Paginated queries generated by `all()`, `filters()`, `get()` and resource pool allocation lookups now pass `offset` and `limit` as GraphQL variables instead of inlining them in the query text. The query document stays identical across pages, allowing the Infrahub server to reuse its cached query analysis, and the query is now rendered once per call instead of once per page. `generate_query_data` also accepts variable placeholder strings (for example `"$offset"`) for its `offset` and `limit` arguments.
- Raised the default `Config.rate_limit_max_retries` from 5 to 10, so a request shed with HTTP 429 keeps retrying (honouring `Retry-After`) for longer before raising `RateLimitError`. This lets background work ride out a longer burst of server-side backpressure. Callers that prefer to give up sooner can lower the value.
- The hand-maintained schema models in `infrahub_sdk.schema` are now backed by the generated write/read contract (`infrahub_sdk.schema.generated`). Public names, import paths, and behavior methods are unchanged, but a few defaults and constraints now match the server contract:

- `AttributeKind.STRING` has been removed. It was deprecated and `kind="String"` was already rejected server-side; use `AttributeKind.TEXT` instead.
- Write and read models drop unknown fields silently (`extra="ignore"`). A submitted field that is not part of the write contract — read-level, internal, or a typo — is dropped rather than rejected, and a read model tolerates additional fields returned by a newer server.
- Write-model defaults now match the server contract: relationship `min_count`/`max_count` default to `0` (was `None`), node `branch` defaults to `"aware"`, `generate_profile` defaults to `True`, and `generate_template` defaults to `False`. This changes the round-trip output of programmatically-built schemas.

Constructing `AttributeSchema(name=..., kind=AttributeKind.TEXT, ...)`, `NodeSchema`, `GenericSchema`, `RelationshipSchema`, `SchemaRoot`, and the read-side `*API` models continues to work unchanged.

## [1.22.3](https://github.com/opsmill/infrahub-sdk-python/tree/v1.22.3) - 2026-08-19

### Fixed

- Register the `infrahub_integration` pytest marker under its real name. It was registered as `infrahub_integraton`, so integration tests raised a `PytestUnknownMarkWarning` on every run and failed to collect under `--strict-markers`. ([#1231](https://github.com/opsmill/infrahub-sdk-python/issues/1231))
- Fixed the `load` and `check` command descriptions in the `infrahubctl schema` help output and generated docs, which were cut off mid-sentence.

## [1.22.2](https://github.com/opsmill/infrahub-sdk-python/tree/v1.22.2) - 2026-07-27

### Added
Expand Down
1 change: 0 additions & 1 deletion changelog/+schema-cli-short-help.fixed.md

This file was deleted.

1 change: 0 additions & 1 deletion changelog/1231.fixed.md

This file was deleted.

32 changes: 32 additions & 0 deletions dev/specs/ihs-259-sdk-x-priority-header/alignment-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Spec / Ask Alignment Check: SDK `X-Priority` Request Header

**Date**: 2026-07-10
**Feature dir**: `specs/ihs-259-sdk-x-priority-header/`

## 1. Source

- **Source PRD**: Jira **IHS-259** — "feat: SDK X-Priority request header" (`https://opsmill.atlassian.net/browse/IHS-259`), fetched via the Atlassian integration. The Jira issue description *is* a full PRD (Problem Statement, Solution Overview, 7 User Stories, FR-001…008, Key Entities, Edge Cases, SC-001…005, Implementation/Testing Decisions, Out of Scope, Assumptions).
- Compared against: `spec.md` (current, post-critique).

## 2. Verdict

✅ **ALIGNED**

The spec faithfully carries every PRD requirement, user story, acceptance criterion, edge case, and out-of-scope boundary. The only differences are expansions of detail and testability clarifications that preserve — and in two cases make verifiable — the PRD's stated intent. No requirement is missing, changed in meaning, dropped, softened, or contradicted.

## 3. Findings

| Severity | Category | PRD reference | Spec reference | Description |
|----------|----------|---------------|----------------|-------------|
| ℹ️ Info (no drift) | mapping | PRD User Stories 1–7 | spec US1–US5 | 7 PRD stories consolidated into 5. All intent preserved: PRD US1→US1, US2 (enum) folded into FR-001 + contracts, US3 (override)→US2, US4 (rides every transport)→US1/FR-003, US5 (zero change)→US3, US6 (invalid rejected)→US4, US7 (async=sync)→US5. Consolidation, not loss. |
| ℹ️ Info (expansion) | added | PRD Assumptions ("header is exactly `X-Priority`") | spec FR-009 | Spec adds FR-009 stating the header name is exactly `X-Priority` with lowercase value. This promotes a PRD assumption to a testable requirement — expansion of detail, within PRD scope. |
| ℹ️ Info (expansion) | added | PRD Edge Cases ("batch mode and raw blob transfers inherit the client default") | spec SC-006 | Spec adds SC-006 verifying batch/blob inherit the configured default. Makes an implicit PRD scope claim testable; does not add new scope (no per-request override for these, matching the PRD). Raised by the critique (P5/X1). |
| ⚠️ Minor (clarified, not softened) | changed-wording | PRD SC-002 ("emits no `X-Priority` header — asserted byte-for-byte against current behaviour") | spec SC-002 | Reworded to "no `X-Priority` emitted; no other SDK-set outgoing header changes (assert `X-Priority` absent; not a literal byte-for-byte comparison of transport-injected headers)". The requirement (no header, no behaviour change) is unchanged; only the assertion method is clarified because httpx injects its own headers, making a literal byte comparison neither stable nor meaningful. Raised by the critique (E6). |

All FR-001…008 map 1:1 to spec FR-001…008. All SC-001, SC-003, SC-004, SC-005 map 1:1. All PRD edge cases and all five Out-of-Scope items (429/#1124, server-side/INFP-636, classification guidance, per-batch/blob knobs, anti-escalation) are present in the spec.

## 4. Action

**Proceed.** No remediation required. The spec is aligned with IHS-259; the two ⚠️/expansion items are testability clarifications that strengthen the spec without departing from the PRD. `tasks.md` (Phase 4) is ready for review and implementation.

- Remediation passes used: **0**.
36 changes: 36 additions & 0 deletions dev/specs/ihs-259-sdk-x-priority-header/checklists/requirements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Specification Quality Checklist: SDK `X-Priority` Request Header

**Purpose**: Validate specification completeness and quality before proceeding to planning
**Created**: 2026-07-10
**Feature**: [spec.md](../spec.md)

## Content Quality

- [x] No implementation details (languages, frameworks, APIs)
- [x] Focused on user value and business needs
- [x] Written for non-technical stakeholders
- [x] All mandatory sections completed

## Requirement Completeness

- [x] No [NEEDS CLARIFICATION] markers remain
- [x] Requirements are testable and unambiguous
- [x] Success criteria are measurable
- [x] Success criteria are technology-agnostic (no implementation details)
- [x] All acceptance scenarios are defined
- [x] Edge cases are identified
- [x] Scope is clearly bounded
- [x] Dependencies and assumptions identified

## Feature Readiness

- [x] All functional requirements have clear acceptance criteria
- [x] User scenarios cover primary flows
- [x] Feature meets measurable outcomes defined in Success Criteria
- [x] No implementation details leak into specification

## Notes

- The spec unavoidably names the concrete wire contract (`X-Priority` header, `Priority` enum, `Config` field, method kwarg) because these ARE the requirement contract handed down from the PRD (IHS-259) and the server-side effort (INFP-636), not free implementation choices. Enum/config/kwarg names are treated as the externally observable API surface, not internal implementation detail.
- No [NEEDS CLARIFICATION] markers were needed: the source PRD is detailed and unambiguous, with resolution rules, transport coverage, and testing decisions all specified.
- All items pass. Spec is ready for `/speckit-plan`.
74 changes: 74 additions & 0 deletions dev/specs/ihs-259-sdk-x-priority-header/contracts/priority-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# API Contract: Priority public surface

**Feature**: IHS-259 | **Scope**: SDK public Python API (async + sync). This is a public-API-signature change (governance-approved in IHS-259).

## New public symbol: `Priority`

```python
from infrahub_sdk.constants import Priority

class Priority(str, enum.Enum):
HIGH = "high"
NORMAL = "normal"

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.

P2: This contract document names the public enum member NORMAL = "normal" and says config accepts high|normal|low, but the actual shipped infrahub_sdk.constants.Priority defines MEDIUM = "medium" (constants.py:20) and Config.priority's description lists high|medium|low (config.py:63), matching the tests' Priority.MEDIUM. A consumer following this contract would reference Priority.NORMAL, which does not exist, so the contract misstates the public API surface. Align the doc with the implementation: use MEDIUM = "medium" and high|medium|low.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At dev/specs/ihs-259-sdk-x-priority-header/contracts/priority-api.md, line 12:

<comment>This contract document names the public enum member `NORMAL = "normal"` and says config accepts `high|normal|low`, but the actual shipped `infrahub_sdk.constants.Priority` defines `MEDIUM = "medium"` (constants.py:20) and `Config.priority`'s description lists `high|medium|low` (config.py:63), matching the tests' `Priority.MEDIUM`. A consumer following this contract would reference `Priority.NORMAL`, which does not exist, so the contract misstates the public API surface. Align the doc with the implementation: use `MEDIUM = "medium"` and `high|medium|low`.</comment>

<file context>
@@ -0,0 +1,74 @@
+
+class Priority(str, enum.Enum):
+    HIGH = "high"
+    NORMAL = "normal"
+    LOW = "low"
+```
</file context>
Suggested change
NORMAL = "normal"
MEDIUM = "medium"

LOW = "low"
```

- `str`-valued closed enum. `Priority("LOW") is Priority.LOW` (case-insensitive via `_missing_`).
- Unknown values raise `ValueError` (→ `pydantic.ValidationError` at config load).
- Imported from `infrahub_sdk.constants` (kept out of the top-level `infrahub_sdk` namespace so importing the enum does not pull in `Config` and the client classes).

## Extended: `Config.priority`

```python
class ConfigBase(BaseSettings):
...
priority: Priority | None = Field(
default=None,
description="Default request priority emitted as the X-Priority header on every request. "
"One of high|normal|low (case-insensitive). When unset, no header is sent.",
)
```

- Env var: `INFRAHUB_PRIORITY`.
- Accepts a `Priority` or a case-insensitive string; unknown → validation error at load.
- Default `None` → no client-wide default.

## Extended method signatures (new `priority` keyword — both `InfrahubClient` and `InfrahubClientSync`)

Each covered method gains `priority: Priority | None = None` (default `None` preserves current behaviour). The argument is keyword-friendly and additive — existing positional/keyword calls are unaffected.

```python
# Client
def get(self, kind, ..., priority: Priority | None = None) -> ...
def all(self, kind, ..., priority: Priority | None = None) -> ... # forwards to filters + count
def filters(self, kind, ..., priority: Priority | None = None) -> ...
def count(self, kind, ..., priority: Priority | None = None) -> int
def execute_graphql(self, query, ..., priority: Priority | None = None) -> dict
def _execute_graphql_with_file(self, ..., priority: Priority | None = None) -> ... # file variant
def create_diff(self, ..., priority: Priority | None = None) -> ...
def get_diff_summary(self, ..., priority: Priority | None = None) -> ...
def get_diff_tree(self, ..., priority: Priority | None = None) -> ...

# Node (InfrahubNode / InfrahubNodeSync)
def save(self, ..., priority: Priority | None = None) -> None
def create(self, ..., priority: Priority | None = None) -> None
def update(self, ..., priority: Priority | None = None) -> None
def delete(self, ..., priority: Priority | None = None) -> None
```

> **Note — `client.create` is intentionally NOT extended.** `InfrahubClient.create()` / `InfrahubClientSync.create()` only build an unsaved `InfrahubNode` in memory and issue no HTTP request, so a `priority=` kwarg there would be a no-op. Per-request priority for creating a node is carried by the node-level `save()` / `create()` (which issue the mutation). Within a node create/update, a resource-pool relationship's follow-up peer fetch inherits the same `priority`.

### Behavioural contract per call

- `priority=None` (default): use the client-wide default (which may itself be `None` → no header). No client state is mutated.
- `priority=Priority.X`: this request carries `X-Priority: x`, overriding the client default for this call only. The next un-annotated call reverts to the client default.
- Resolution: `resolved = per_request if per_request is not None else client_default`.

## Explicitly NOT extended (v1)

- `_get`, `_post`, `_get_streaming` (raw blob transfers) — inherit the client default only; no `priority` kwarg.
- Batch mode — inherits the client default only; no per-call override.

## Backwards-compatibility guarantee

- Adding a keyword-only-friendly parameter with a `None` default and a new optional config field is additive. Any existing caller that sets nothing sees no change in outgoing requests (FR-004 / SC-002).
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Wire Contract: `X-Priority` HTTP header

**Feature**: IHS-259 | **Consumer**: Infrahub API server (INFP-636)

## Header

| Property | Value |
|----------|-------|
| Name | `X-Priority` (exact, case-insensitive on the server per HTTP header rules) |
| Values | `high`, `normal`, `low` (lowercase emitted by the SDK) |
| Cardinality | 0 or 1 per request |

## Emission rules (SDK side)

1. The SDK emits the header on a request **iff** the resolved priority for that request is non-`None`.
2. When emitted, the value is exactly the lowercase token of the resolved `Priority` member.
3. The header is emitted uniformly across every transport when a client-wide default is configured: GraphQL query/mutation, multipart file upload, and raw blob `_get`/`_post`.
4. When no priority is configured and none is passed per request, the header is **absent** — the outgoing request is byte-for-byte identical to the pre-feature SDK.

## Server semantics (assumed, per INFP-636 — not implemented here)

- The server treats the value case-insensitively.
- An **absent** header and an **unknown** value are both treated as `normal`.
- Consequently, "omit the header" and "send `normal`" are server-equivalent, which is what makes omitting-when-unconfigured a safe, non-breaking rollout.

## Non-goals (this contract)

- No `Retry-After` / 429 semantics (GitHub #1124).
- No server-side admission control, routing, or throttling behaviour (INFP-636).
- The SDK does not read or react to any response header related to priority.
Loading