Skip to content

feat: keep withheld resources as slim reference mappings - #300

Merged
Ruari-Phipps merged 6 commits into
mainfrom
feat/slim-resource-references
Sep 1, 2026
Merged

feat: keep withheld resources as slim reference mappings#300
Ruari-Phipps merged 6 commits into
mainfrom
feat/slim-resource-references

Conversation

@Ruari-Phipps

@Ruari-Phipps Ruari-Phipps commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

Summary

Withheld resources whose ids appear inside resources gated on a different permission
are kept as identity-only mappings rather than dropped, so {{entity:...}}-style
references to them still resolve to a name. They live in slim_resources, a list of
ResourceMapping that resolves references but is never iterated for a file, saved, or
pushed.

Motivation

With #299 alone, a restricted reader's pull succeeds but every reference to a withheld
resource renders as a raw id — and because the id round-trips differently than the name,
the file looks locally modified on every command. The mapping also has to survive in the
status file: every command other than pull rehydrates from it, so a slim resource not
written there is gone by the next command.

Changes

  • from_projection for entities, functions, handoffs, SMS templates, translations,
    variants and variant attributes returns identity-only stubs (slim=True) for
    auth-filtered slices; load_resources_from_projection separates them out of the
    resource map into a list of ResourceMapping.
  • Variables are treated as slim when functions are slim: variableUpdate is gated on a
    different permission than functions, so the API would accept a reference graph rebuilt
    from functions the user cannot see.
  • ResourceMapping gained to_dict/from_dict, storing resource_type by its
    registered name so slim mappings persist in the status file. An unregistered type is
    dropped on read rather than raising.
  • Slim resources are excluded from file_structure_info — they have no file on disk, so
    a baseline entry would make find_new_kept_deleted report them deleted on every run.
  • pull_resources, pull_deployment_resources, pull_branch_resources,
    get_template_resources, get_remote_resources_by_name and
    _resolve_branch_fork_point all return their slim mappings alongside the resource
    map, and every caller (pull, push, status, diff, branch diff, validate, sync-ids) was
    updated to resolve references against them.
  • Tests covering status-file round-trips, force-pull removal of a type that became
    withheld, and cache/disk agreement after a pull.

Test strategy

  • Added/updated unit tests
  • Manual CLI testing (poly <command>)
  • Tested against a live Agent Studio project
  • N/A (docs, config, or trivial change)

Checklist

  • ruff check . and ruff format --check . pass
  • pytest passes (1453 passed, 165 subtests)
  • No breaking changes to the poly CLI interface (or migration path documented)
  • Commit messages follow conventional commits

@Ruari-Phipps
Ruari-Phipps requested a review from a team as a code owner September 1, 2026 10:37
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@Ruari-Phipps
Ruari-Phipps force-pushed the feat/slim-resource-references branch from e2e7451 to 9a9aa63 Compare September 1, 2026 11:06
@github-actions

This comment has been minimized.

Base automatically changed from fix/auth-filtered-projection-guards to main September 1, 2026 14:31
Ruari-Phipps and others added 6 commits September 1, 2026 15:33
Dropping a withheld resource type entirely leaves every
{{entity:...}}-style reference to it rendering as a raw id, and the
file then looks locally modified on every command. Withheld resources
whose ids appear inside resources gated on a different permission are
now kept as identity-only stubs, separated out of the resource map into
slim_resources - a list of ResourceMapping that resolves references but
is never iterated for a file, saved, or pushed.

- ResourceMapping gained to_dict/from_dict, storing resource_type by
  its registered name, so slim mappings persist in the status file. An
  unregistered type is dropped on read rather than raising.
- Slim resources are excluded from file_structure_info - they have no
  file on disk, so a baseline entry would make find_new_kept_deleted
  report them deleted on every run.
- pull_resources, pull_deployment_resources, pull_branch_resources,
  get_template_resources, get_remote_resources_by_name and
  _resolve_branch_fork_point all return their slim mappings alongside
  the resource map, and every caller was updated to match.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
pull_project overwrote self.slim_resources with the incoming list before
_update_pulled_resources built the original resource mappings, so the
baseline resolved references against the incoming slim set. The two
diverge when a withheld resource was renamed remotely or permissions
changed - the baseline then renders with the new name while the disk
file carries the old one, a phantom local change at merge time.

_update_pulled_resources now takes both slim lists explicitly and every
caller passes the list that matches each side.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
RESOURCE_NAME_TO_CLASS.get("variables") returning None would make the
isinstance check raise TypeError, and .get("functions") returning None
fails the other way - silently degrading to "functions are not slim",
which is the unsafe direction. Import the classes and use them directly;
the deferred import avoids the module-level cycle.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
FlowConfig can never be slim - a filtered flows slice returns no flow
configs at all - so the string-typed special cases were unreachable.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The rationale lived only in a test docstring; the one non-mechanical
rule in _filter_slim_resources deserves its why next to it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ResourceMapping.from_dict called cls(**data) on a dict straight off the
status file. One unexpected key - a hand edit, or a field written by a
newer ADK - raised TypeError and killed every command. Unknown keys are
now dropped and an entry missing required fields loses that one mapping
instead of aborting, matching how an unregistered resource type is
already handled.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Ruari-Phipps
Ruari-Phipps force-pushed the feat/slim-resource-references branch from 9a9aa63 to e9d661a Compare September 1, 2026 14:35
@Ruari-Phipps
Ruari-Phipps enabled auto-merge (squash) September 1, 2026 14:36
@Ruari-Phipps
Ruari-Phipps merged commit d8772cb into main Sep 1, 2026
6 checks passed
@Ruari-Phipps
Ruari-Phipps deleted the feat/slim-resource-references branch September 1, 2026 14:37
@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Coverage Report

Base (main) PR Change
76.9% 77.4% +0.4% ✅

Changed file coverage

File Coverage Change
poly/utils/decorators.py 62.5% +33.3% ✅
poly/handlers/interface.py 64.2% -0.1% ⚠️
poly/project.py 77.6% +1.2% ✅
poly/resources/function.py 82.2% +0.2% ✅
poly/resources/handoff.py 85.8% +0.4% ✅
poly/resources/variant_attributes.py 86.4% +0.6% ✅
poly/resources/entities.py 88.4% +0.4% ✅
poly/resources/languages.py 89.1% -0.4% ⚠️
poly/resources/resource.py 91.1% +0.7% ✅
poly/resources/sms.py 91.1% +0.4% ✅
poly/resources/translations.py 94.0% +0.1% ✅
poly/utils/stub_gen.py 95.9% +42.5% ✅

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.

2 participants