Skip to content

feat(templates): materialize a template's declared schedules: at agent creation (trinity-enterprise#89) - #1946

Merged
vybe merged 15 commits into
devfrom
feature/ent89-template-schedules
Aug 3, 2026
Merged

feat(templates): materialize a template's declared schedules: at agent creation (trinity-enterprise#89)#1946
vybe merged 15 commits into
devfrom
feature/ent89-template-schedules

Conversation

@AndriiPasternak31

Copy link
Copy Markdown
Contributor

Refs trinity-enterprise#89

A template's template.yaml can declare a schedules: block. The abilities
plugin ecosystem treats that block as an agent's design source of truth — but
only /trinity:sync ever reconciled it. Creation via the UI, the API, or MCP
materialized none of it: you created an agent from a template that declared
its own cadence, and it sat there with an empty Schedules tab. This closes that,
for both github: and local: sources, with every declared schedule landing
disabled by default (issue AC — the operator opts in).

Cross-tracker issue — GitHub auto-closes same-repo only, so ent#89 is closed
manually at release (working agreement per #1760). The private issue also
needs status-in-dev set by hand after merge.

The shape

A new tolerant leaf, services/template_schedules.py, is the single reader —
shared by both catalog builders, the creation-time materializer, and the new
compat check, so the report can never drift from what creation actually does.
It is total by contract: a raise would empty the catalog, enter creation's
rollback fence, or fail-open the compat check. Bounds (MAX_DECLARED_SCHEDULES=20,
name/description/message limits), intra-block name dedupe, and strict cron via
schedule_validation.validate_cron_expression — the scheduler's own parser,
not a second regex.

The declared list rides a normalized _TemplateResolution.declared_schedules
carrier, deliberately not folded into template_data. That field is raw YAML
on the local: path and {} on the github: path (which has never populated
it — which is why #383's persistent_state and #1169's data_paths are
effectively local:-only), and its truthiness gates credential-file generation.
Reusing it would have coupled schedules to a field that means two different
things and load-bears a third.

The bit worth reviewing — the github: half needs its own fetch

The catalog's cached metadata is read with the global platform PAT off the
default branch. Materializing from that copy means a creator using a
per-user token against a private repo, or pinning @branch, would have
got zero schedules with no signal — the exact silent-ignore class this
feature exists to close, reintroduced one layer up. So the github: branch does
its own creation-path read: fetch_template_metadata_for_create(repo, pat, ref)
— resolved PAT, parsed @branch ref, cache-bypassed, and loud on any
failure.

Three regressions this change would otherwise have created

  • Both GitHub catalog list paths are now fenced per-template. They were bare
    list comprehensions, so one raise in the untrusted GitHub builder 500'd the
    whole GitHub half of GET /api/templates. ent#128 PR-A fenced only the local
    path; adding a second declarative reader to the unfenced one is what made this
    reachable.
  • system_service.create_schedules gained a name-match guard. It runs
    after create_agent_internal, so post-ent#89 it is the second schedule
    producer for the same agent — a manifest declaring a name the template already
    materialized would have double-created.
  • db.create_schedule's falsy return is now checked. It returns None on
    three paths and never raises, so an unchecked call reports success on failure.

Compat: T-018, and the fail-open class it exposed

T-018 (soft, static) reports the schedules: block's structure only
cron stays A-002's, because two checks disagreeing on one field is worse than
either. It is the one check that fails closed, and the reason is the bug
class it sits on: run_static converts a raise into skipped and _counts
counts only fail, so a raising soft check drops soft_count 1→0 and — since
overall is a bare > 0 test — flips issues → compatible exactly when its
finding was the only failure. _report_from_persisted then replays that from
checks_json on every stopped-agent read.

Two live instances found and fixed in radius:

  • c_p006 (a HARD check) iterated schedules with no isinstance(..., list)
    guard, unlike its four siblings — so schedules: 5 silently vanished from
    hard_count.
  • _valid_cron (A-002) was a per-field regex wrong in both directions:
    it rejected 0 9 * * MON and accepted 99 99 * * *. Now delegates to the
    scheduler's own validator.

run_static's swallow now logs (it was silent for all ~100 checks). Converting
it to fail platform-wide is a measured follow-up, not this change.

Validation

  • 6,413 unit tests pass (full tests/unit suite, order-independent under
    pytest-randomly). +391 new across three ent#89 files.
  • The one deselected failure (test_1069_voip_call_path_param) reproduces
    identically on pristine origin/dev (c4c83f4) — a FastAPI-version drift
    in the local venv, not a regression from this branch.
  • Ruff: 4 findings on the touched files, all four byte-identical and
    pre-existing on dev (line numbers shifted only). The new
    template_schedules.py is ruff-clean.
  • enterprise-docs-guard pattern run locally against docs/ + the seam files:
    clean.
  • No schema change (no dual-track migration owed), no new endpoint, no new
    os.getenv, no compose/Dockerfile change.

Scope

OSS-core — no entitlement gate, consistent with the ent#123 / ent#128 / ent#46→#118
precedent for enterprise-tracker features built in core code.

Docs: requirements §10.16, architecture.md, four feature flows
(template-processing, scheduling, agent-compatibility-validation, system-manifest)

  • the index row, docs/agent-validation-spec.md (T-018), learnings.md, and the
    CSO diff audit under docs/security-reports/.

🤖 Generated with Claude Code

AndriiPasternak31 and others added 14 commits August 2, 2026 19:04
…ent#89)

Requirements-first per Rule of Engagement #1 — written and committed before
any implementation.

Covers the declared `schedules:` contract, the total-function reader and its
tolerance matrix, the normalized carrier that feeds BOTH resolver branches
(the `github:` half needs the creation-resolved PAT + parsed ref, not the
catalog's global-PAT cached fetch), the honored-`enabled` decision and its
`set_autonomy_status` caveat, idempotency in all three places creation /
intra-block / manifest deploy, and the T-018 + A-002 + c_p006 compatibility
half.

Refs trinity-enterprise#89

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
New leaf `services/template_schedules.py`: `schedule_shape_errors` +
`normalize_declared_schedules` over one private `_parse`, mirroring the
sibling `credential_shape_errors` / `credential_mcp_server_names` convention
(ent#128).

The contract is TOTALITY — template.yaml is untrusted and `yaml.safe_load()`
can yield a scalar, list or mapping at any level, so a raise here would empty
the template catalog (#1835 class), enter the creation rollback fence, or
fail-open the T-018 check. Every shape degrades to a safe value plus a named
error.

Cron and timezone are validated with the SAME parser the dedicated scheduler
registers with (#1472) because `_calculate_next_run_at` swallows a bad cron
and `set_schedule_enabled` never re-validates — an unvalidated entry would
become a zombie schedule that exists, shows no next run, and can never fire.

Errors name the index, the key and a YAML type — never the `name`/`message`
VALUE, which is unbounded and lands in a persisted, UI-rendered blob. The
cron/timezone strings are the one echo, bounded and printable-filtered by a
local twin of `_sanitize_for_warning` (importing it would close a cycle).

Refs trinity-enterprise#89

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…tHub list paths (ent#89)

Three changes in one file:

1. `_build_template` (GitHub) and `_build_local_template` (local) both surface
   `schedules` (normalized) + `schedule_errors`. BOTH, explicitly — the
   pre-existing asymmetry (`persistent_state` is surfaced only by the GitHub
   builder) means parity cannot be assumed, and AC #2 covers both sources.

2. R3 — both GitHub catalog list paths were BARE list comprehensions.
   ent#128 PR-A fenced `_build_local_template` only, so adding an
   untrusted-input reader to `_build_template` would have put a new
   raise-capable call on an unfenced path: one malformed repo would 500 the
   whole GitHub half of GET /api/templates. That is the #1835 bug this
   feature is modelled on, re-opened by the feature itself. Both are now
   fenced per-template via `_safe_build_github_template`.

3. `fetch_template_metadata_for_create` — the creation path must NOT read the
   catalog cache. That cache uses the GLOBAL platform PAT (creation resolves
   per-agent -> per-user -> global, ent#162), sends no `?ref=` (so an
   `@branch` create would read the default branch), and is a 10-minute
   per-process TTL. The new fetch takes the resolved PAT + parsed ref, and is
   loud on every failure — a silently empty declaration on the `github:` path
   is the exact class this feature exists to close.

Refs trinity-enterprise#89

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… (ent#89)

`_TemplateResolution.declared_schedules` is a normalized carrier populated by
BOTH resolver branches, so AC #2's "GitHub and local" is real rather than
nominal. It is deliberately not folded into `template_data`: that field is raw
template YAML on the `local:` path and `{}` on the `github:` path — which has
never populated it, so #383's `persistent_state` and #1169's `data_paths` are
effectively `local:`-only — and `_stage_config_files` gates credential-file
generation on `if template_data:`, so merging the two shapes would change
credential generation for every GitHub agent.

`reconcile_declared_schedules` is shaped as a reconcile primitive (takes
`agent_name`, not `AgentConfig`) so a future "re-apply template" can reuse it.
No recreate hook is added — an eager re-materialize would resurrect schedules
an operator deliberately deleted.

Two failure modes handled explicitly:
- `db.create_schedule` RETURNS None on three paths (unknown user, no access,
  the #1445 is_agent_live gate) and never raises, so a try/except alone would
  catch nothing and a length-derived counter would report schedules that were
  never written. The return value is checked and counted as failed.
- The whole step is non-fatal and the try/except wraps the entire call
  including `list_agent_schedules` — this function sits inside the destructive
  rollback fence, so an escaping raise would roll back a successful creation
  over a schedule.

`enabled` is passed explicitly (ScheduleCreate defaults it to True, which
would invert AC #3), and ghosts are skipped at the caller per ent#69 fleet
hygiene.

Refs trinity-enterprise#89

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…lized (ent#89)

`deploy_manifest` creates each agent and THEN calls `create_schedules`, so
post-ent#89 it is the second schedule producer for the same agent: the first
call materializes the template's declared block, the second adds the
manifest's. With no UNIQUE(agent_name, name) index, a manifest declaring
`daily-briefing` on a template that also declares it produced two rows.

This is a regression ent#89 itself creates, so the guard ships with it rather
than as a follow-up. Fails open on a read error — dropping a manifest's
schedules would be worse than the duplicate this prevents.

Refs trinity-enterprise#89

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ctions (ent#89)

T-018 (SOFT, STATIC) reports the `schedules:` block's STRUCTURE only, sharing
one reader with the materializer and the catalog surface so the report cannot
drift from what creation actually does. It deliberately does not report cron
syntax — see correction 2.

It fails CLOSED, against the grain of every other check: `run_static` turns a
raise into `skipped` and the report counts only `fail`, so a raising SOFT
check drops soft_count 1->0 and flips overall_status issues->compatible
exactly when its finding was the only failure — the entire population T-018
exists to serve — and `_report_from_persisted` then replays that clean bill of
health from checks_json on every stopped-agent read. Detail carries the
exception TYPE only; `str(e)` can embed untrusted template content into a
persisted, UI-rendered blob.

Three corrections, all in blast radius:

1. `c_p006` was missing the `isinstance(..., list)` guard its four sibling
   readers of this field all carry. `schedules: 5` raised TypeError -> swallowed
   -> a HARD check silently vanished from hard_count. A live instance of the
   exact class T-018 guards against, which is what makes the fail-closed design
   evidence-backed rather than theoretical.

2. `_valid_cron` (A-002) was a per-field `^[\d*/,\-]+$` regex, wrong in BOTH
   directions: it rejected `0 9 * * MON` and accepted `99 99 * * *`. It now
   delegates to `validate_cron_expression` — the same parser the scheduler
   registers with (#1472) and the same one the ent#89 reader gates on. One
   cron authority, agreeing with the executor.

3. `run_static`'s swallow now logs. It previously left no trace anywhere, for
   all ~100 checks; this is the instrument for deciding later whether to flip
   it to `fail` platform-wide.

Catalog 100 -> 101.

Refs trinity-enterprise#89

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… (ent#89)

The error list is persisted into agent_compatibility_results.checks_json,
rendered in the UI, and returned in the catalog response, so the discipline is
index + key + type name only. The entry index already identifies the offender;
the name added disclosure without adding actionability. Cron and timezone stay
the only echoed values — bounded, printable-filtered, and what makes those
particular errors fixable.

Refs trinity-enterprise#89

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three new files plus compatibility extensions, 143 tests.

test_ent89_template_schedules.py — the reader's tolerance matrix (40 rows),
error-string discipline (no name/message/description ever echoed; cron
sanitized and bounded), the catalog surface in BOTH builders, the GitHub
list-path fence, and the create-path fetch. Plus a Hypothesis property over
recursive JSON-ish values asserting totality — a 40-row matrix cannot be a
totality proof for `yaml.safe_load` output, and totality is this module's
entire contract.

test_ent89_schedule_materialization.py — REAL DB rows via db_harness, not
`mock.assert_called`. Both recorded lessons apply here: a mock-`db` suite is
blind to a facade gap, and a parameter only one branch consumes is a severed
wire a mock will happily confirm — which is precisely the failure history of
the `github:` half of AC #2. So both resolver branches are driven for real,
and the github fetch's PAT and ref are asserted; without that the §0
regression test would be self-attestation.

test_ent89_manifest_no_duplicate.py — R5, in creation order: template first,
manifest second, one row, and the template's row is not overwritten.

test_compatibility_checks.py — T-018 pass/fail/absent, its fail-closed branch
(and that `detail` never carries `str(e)`), a `build_report`-level test
pinning the DIRECTION (a raising reader must still yield `overall_status ==
"issues"`), the `_report_from_persisted` recompute, A-002 in both directions
and agreeing with the materializer, c_p006 on `schedules: 5`, the run_static
log, and the whole static catalog run against 7 hostile templates asserting no
check lands at `check_error` — a T-018-only assertion would never have caught
c_p006.

The two fence tests pin `get_github_templates` by sys.modules KEY rather than
by module object: `get_all_templates` imports it lazily inside the function,
so an attribute patch on a separately-imported reference passed in isolation
and failed under the full suite, where an earlier file swaps that object.

Full unit suite: 6405 passed. The one red, test_1069_voip_call_path_param, is
a pre-existing venv FastAPI drift (`get_flat_dependant` no longer exported) in
a file this branch does not touch.

Refs trinity-enterprise#89

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…n class (ent#89)

- template_service bullet: the `schedules:` reader beside the `credentials:`
  ones, the newly fenced GitHub catalog list paths, and why the creation path
  needs its own metadata fetch rather than the global-PAT default-branch cache.
- New `template_schedules.py` leaf bullet with its totality contract.
- crud bullet: the `declared_schedules` carrier, why it is not `template_data`,
  and the non-fatal reconcile step.
- Compatibility block: T-018, why it is the one check that fails closed, and
  the two live instances of that class it fixes (c_p006, _valid_cron/A-002).
- template.yaml file-tree note now names its four declarative blocks.

Refs trinity-enterprise#89

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… (ent#89)

- template-processing.md: the `schedules:` reader beside the `credentials:`
  ones, the three deliberate differences (normalized surface, both GitHub list
  paths fenced, creation does not read the catalog's copy), and the
  error-string discipline.
- scheduling.md: new flow 1c — the fourth schedule producer, why the carrier
  is not `template_data`, why non-fatality is the invariant, the falsy-return
  check, and idempotency in all three places.
- agent-compatibility-validation.md: T-018, why it is the one check that fails
  closed, the A-002 cron-authority consolidation, and the c_p006 live instance.
- feature-flows.md: Recent Updates row (required even when the flow docs
  already existed).
- learnings.md: the durable half — a per-item swallow plus a count that ignores
  skips makes a validator's own bug invisible, and persistence replays it.

Refs trinity-enterprise#89

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two gaps the /update-tests coverage checklist surfaced:

- Every other create-path-fetch test stubs `_fetch_template_yaml_result`,
  i.e. BELOW the HTTP layer — so a wrong param name (`?ref=` is what pins the
  revision) or a dropped Authorization header would leave them all green while
  the feature silently read the default branch, or read nothing for a private
  repo. That is precisely the R2 failure this fetch exists to prevent, so it
  is now asserted at the wire, including the ent#123 tokenless case sending no
  Authorization header at all.

- The unit suite proves both BUILDERS emit `schedules`/`schedule_errors`; only
  a live call proves the router serves them, that entries are the normalized
  shape, and that a template reporting errors is still LISTED — the
  ent#128/#1835 property, which now also covers the two newly-fenced GitHub
  list paths.

Unit run: 208 passed across the four ent#89-touched unit files.

Refs trinity-enterprise#89

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
/sync-feature-flows caught what the plan had deliberately trimmed: the
`create_schedules()` section documents a function whose behaviour this change
alters. Its docstring block gave no hint that the function is now the SECOND
schedule producer for the same agent.

Adds the name-match skip, why it exists (no UNIQUE(agent_name, name) index and
adding one is a dual-track schema change that would fail on installs already
holding duplicates), that the skip does not overwrite the template's row
including its `enabled` value, and that an unreadable existing set fails open.
Index row now lists the flow too.

Noted, not acted on: `feature-flows.md` Recent Updates carries 65 dated rows
against the ~20 cap #1360 set — pre-existing drift, out of scope here.

Refs trinity-enterprise#89

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`fetch_template_metadata_for_create`'s WARNING interpolated `reason` raw while
its two neighbours on the same call are `_sanitize_for_warning`-wrapped. The
value embeds `str(e)`, and an httpx error message carries the request URL —
i.e. the caller-supplied `owner/repo`. A repo with no `/` skips
`_GITHUB_REPO_PATH_RE` upstream, so control bytes do reach that line.

Bounded at 200 rather than the 80 default: this WARNING exists to be
diagnosable, and an 80-char truncation defeats its purpose.

Raised as a code-consistency item by the /review pass; the CSO diff audit
discarded it as a finding under hard exclusion #9 (log spoofing), so this is
hygiene inside one call, not a security fix.

Tests pin both halves (control-char stripping and the length bound) and both
fail without the change. Also renames a stale `_template_schedule_errors()`
reference in the requirements doc and cites #1945 for the autonomy-clobber
follow-up the doc previously promised without an issue number.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PASS with 1 MEDIUM (LLM cost amplification: declared schedules meeting the
pre-existing `set_autonomy_status` clobber). No CRITICAL, no HIGH. The finding
is an amplification of a pre-existing mechanism — there has never been a
per-agent schedule cap — so it is filed as #1945 rather than changing this PR.

Matches the convention of the 98 reports already tracked here; the file's Trend
section is only meaningful alongside its siblings.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@AndriiPasternak31
AndriiPasternak31 marked this pull request as ready for review August 2, 2026 20:42
@AndriiPasternak31 AndriiPasternak31 self-assigned this Aug 3, 2026
@AndriiPasternak31
AndriiPasternak31 requested a review from vybe August 3, 2026 00:34
@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

⚠️ Nightly unit-suite check skipped — merge conflict against dev.

Resolve by running git merge dev locally and pushing the result. The next nightly run will re-test once the conflict is gone.

dev has since taken #1913/#1937/#1947/#1949/#1899. Seven conflicts, resolved so
that no side's change is lost:

SOURCE
- static_checks.py  the one real semantic conflict. ent#128 (#1899) flipped the
                    per-check swallow from _skip to _fail so a crashed check is
                    counted by _counts; ent#89 kept _skip and added logging.
                    Taking this branch's side verbatim would have silently
                    reverted the HARD-count fix. Merged: _fail from #1899 +
                    logger.error(exc_info=True) from ent#89, which is strictly
                    more diagnostic than the logger.warning it replaces. The
                    docstring directly above already asserts "a check that could
                    not evaluate is not a check that passed".
- template_service  three hunks, all adjacent additions: both import blocks kept
                    (template_schedules got its own statement — the two sides
                    shared a closing paren), both new functions kept, and both
                    pre-literal computations kept at each of the two call sites.
- crud.py           import list, both symbols kept.

DOCS
- architecture.md   two hunks where BOTH sides had edited the same three bullets
                    (template_service / fork_to_own / crud). Not a pick — each
                    line was 3-way merged at word granularity against the merge
                    base; no edit pairs overlapped, so both sides' text survives
                    verbatim. dev's bullet order preserved, ent#89's new
                    template_schedules.py bullet appended.
- feature-flows.md  all rows kept, table stays reverse-chronological.
- learnings.md      both sets of entries kept.
- registry.json     both entry lists kept. The conflict opened after a bare '{'
                    and closed before a bare '}', so each side was an object
                    BODY -- a naive concatenation produced invalid JSON. Re-added
                    the '},{' separator. 109 -> 112 entries, none dropped.

Verified: zero markers tree-wide, registry.json parses (112 entries), all three
touched modules compile, and every deletion vs origin/dev is one of ent#89's own
intended replacements (crud docstring three->four, the cron helper replaced by
the shared validator, T-018 wired into the dispatch map).

@vybe vybe 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.

/validate-pr: PASS.

  • Base dev ✅ · 23 files ✅ · security scan clean ✅ · no new top-level backend module, no new os.getenv()
  • Docs: requirements §10.16 + architecture + 5 feature flows + a CSO diff audit ✅ · 6 test files ✅

The safety design is the right one, and it composes correctly with what just landed:

  • enabled defaults to False and only a real bool arms a schedule — enabled: "no" is truthy in Python, so a loose read would arm a schedule its author meant to leave off.
  • MAX_DECLARED_SCHEDULES = 20 sits in the reader, not the materializer, so the catalog surface and creation inherit one bound; name/description/message caps close the unbounded-TEXT hole (ScheduleCreate carries no Field constraints).
  • _safe_echo is applied to cron/timezone only. Never echoing name/message/description is correct — those are unbounded, prompt-injection-shaped, and this list is persisted into agent_compatibility_results.checks_json and rendered in the UI.
  • One private _parse behind both public functions means the reported errors and the accepted entries are structurally unable to disagree.
  • The materialize call is wrapped whole — including the helper's own list_agent_schedules read — because it sits inside the destructive rollback fence; a raise there would roll back a successful agent creation over a schedule.

Worth noting the interaction: this makes a template able to mint up to 20 recurring autonomous turns, which is exactly the risk #1949 (just merged) removed by making the autonomy toggle a gate rather than a bulk edit. Landing them together is what keeps declared schedules default-off in practice — before #1949, one unrelated autonomy toggle would have armed all 20.

Conflict resolution (mine), pushed as 4e36d284. Seven conflicts. One was a real semantic conflict rather than an adjacency:

static_checks.py#1899 flipped the per-check swallow from _skip to _fail so a crashed check is counted by _counts; this branch kept _skip and added logging. Taking this branch's side verbatim would have silently reverted #1899's HARD-count fix, restoring "broken validator reports healthy". Merged instead: _fail from #1899 plus this branch's logger.error(..., exc_info=True), which is strictly more diagnostic than the logger.warning it replaced. The docstring immediately above already asserts "a check that could not evaluate is not a check that passed", so the merged form is what that text describes.

The rest: template_service.py ×3 and crud.py ×1 were adjacent additions (both kept; template_schedules needed its own import statement since the two sides shared a closing paren, and both pre-literal computations were kept at each call site). architecture.md had both sides editing the same three bullets — 3-way merged at word granularity against the merge base rather than picked, no edit pairs overlapped, so both texts survive verbatim. registry.json's conflict opened after a bare { and closed before a bare }, so each side was an object body and a naive concatenation produced invalid JSON; re-added the },{ separator (109 → 112 entries, none dropped).

Verified: zero markers tree-wide, registry parses, all three touched modules compile, and every deletion vs origin/dev is one of this branch's own intended replacements.

⚠️ Cross-tracker: ent#89 needs status-in-dev by hand.

@vybe
vybe enabled auto-merge (squash) August 3, 2026 10:21
@vybe
vybe merged commit 01285b8 into dev Aug 3, 2026
22 checks passed
vybe pushed a commit that referenced this pull request Aug 3, 2026
#1899 (this branch's parent) was squash-merged, and #1946 landed after it, so
the stack surfaced as 7 conflicts. Every one is additive:

SOURCE (all three are content this branch simply does not have — ent#89's)
- crud.py             fetch_template_metadata_for_create import
- template_service    the template_schedules import, _template_schedules(), and
                      its two call sites
- static_checks.py    the run_static swallow. This branch carried #1899's
                      logger.warning; dev now has the merged form from #1946 —
                      logger.error(exc_info=True) + _fail. Took dev's: the
                      _fail is #1899's own HARD-count fix and must not regress.

DOCS
- architecture.md     dev's line is a strict superset (same two bullets plus
                      ent#89's template_schedules.py); ent#128's own bullet is
                      byte-identical on both sides.
- feature-flows.md    all four rows kept, table stays reverse-chronological.
- learnings.md        both sets of entries kept.
- credentials.md      §3.6 is this branch's alone; dev added nothing.

Verified: zero markers tree-wide, all five touched modules compile, and
`git diff origin/dev` reports ZERO deletions in all seven conflicted files —
this merge is purely additive.
vybe pushed a commit that referenced this pull request Aug 3, 2026
#1899 (ent#128) and #1946 (ent#89) were both green in isolation and both merged
to dev, but four ent#89 tests assert against pre-#1899 API and behaviour, so the
INTEGRATION is red on dev. Nothing in the production code is wrong; the
assertions describe a contract that #1899 deliberately changed.

Caught by the regression-diff job, which runs after the four required checks and
therefore after auto-merge had already landed #1946 (dev's required set is
Analyze x2 + schema-parity + verify-non-root; it runs no unit tests).

tests/unit/test_compatibility_checks.py

  test_a_raising_check_is_logged
    run_static's swallow now returns _fail(..., {"check_error": ...}), not
    _skip(..., "check_error"), so there is no skip_reason to read. Asserts the
    FAIL shape instead -- which is the point of the change, since only a counted
    status reaches hard_count/soft_count. A sibling in the same file already
    asserted "fail" and was passing, so the file was internally inconsistent.

  test_persisted_check_error_does_not_replay_as_clean
    Asserted soft_count == 0 for a persisted skipped+check_error row -- i.e. it
    pinned the BUG as expected behaviour. #1899's _did_not_pass counts that row
    as a finding on purpose, so the correct expectation is 1. Kept as a real
    regression test: a row persisted by an OLDER build, before the swallow
    started returning fail, still must not replay as clean.

tests/unit/test_ent89_template_schedules.py (x2)

    _build_local_template gained a required keyword-only is_bundled parameter in
    #1899 (it selects the credential-metadata trust label). Both call sites
    updated; orthogonal to the schedules: block under test.

Verified: test_compatibility_checks.py + test_ent89_template_schedules.py +
test_ent128b1_compat_gates.py -> 225 passed, 1 skipped. Full tests/unit on this
branch -> 6698 passed (the four hypothesis property files need a dependency
absent from this host and were excluded).
vybe pushed a commit that referenced this pull request Aug 3, 2026
…1957)

#1899 (ent#128) and #1946 (ent#89) were both green in isolation and both merged
to dev, but four ent#89 tests assert against pre-#1899 API and behaviour, so the
INTEGRATION is red on dev. Nothing in the production code is wrong; the
assertions describe a contract that #1899 deliberately changed.

Caught by the regression-diff job, which runs after the four required checks and
therefore after auto-merge had already landed #1946 (dev's required set is
Analyze x2 + schema-parity + verify-non-root; it runs no unit tests).

tests/unit/test_compatibility_checks.py

  test_a_raising_check_is_logged
    run_static's swallow now returns _fail(..., {"check_error": ...}), not
    _skip(..., "check_error"), so there is no skip_reason to read. Asserts the
    FAIL shape instead -- which is the point of the change, since only a counted
    status reaches hard_count/soft_count. A sibling in the same file already
    asserted "fail" and was passing, so the file was internally inconsistent.

  test_persisted_check_error_does_not_replay_as_clean
    Asserted soft_count == 0 for a persisted skipped+check_error row -- i.e. it
    pinned the BUG as expected behaviour. #1899's _did_not_pass counts that row
    as a finding on purpose, so the correct expectation is 1. Kept as a real
    regression test: a row persisted by an OLDER build, before the swallow
    started returning fail, still must not replay as clean.

tests/unit/test_ent89_template_schedules.py (x2)

    _build_local_template gained a required keyword-only is_bundled parameter in
    #1899 (it selects the credential-metadata trust label). Both call sites
    updated; orthogonal to the schedules: block under test.

Verified: test_compatibility_checks.py + test_ent89_template_schedules.py +
test_ent128b1_compat_gates.py -> 225 passed, 1 skipped. Full tests/unit on this
branch -> 6698 passed (the four hypothesis property files need a dependency
absent from this host and were excluded).

Co-authored-by: trinity-ability <309458136+trinity-ability@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants