Per-command TANGLE_ROOT_CONFIG layered beneath --config; remove ArgsContainer.origin() (v0.1.24) - #67
Merged
Volv-G merged 1 commit intoSep 25, 2026
Conversation
…ontainer.origin() (v0.1.24) *(AI-assisted)*
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
(AI-assisted)
What
TANGLE_ROOT_CONFIGnames a per-command root config. The entry for the running command is layered beneath that command's--config.ArgsContainer.origin()is removed (it was provisional and unused).How
Command selector shape
commands. Besides it, only underscore-prefixed helper keys (for YAML anchors) may appear there. Anything else is an error: a missingcommands, another top-level key, a non-mapping entry, or a key that duplicates another after normalization._select/_envresolve in this order:_selectcan choose between wholecommandsdocuments;_select;_envvalues are read only for the running command, but every directive in the file is structure-checked in every environment.Explicit command identity
ArgsContainer.load(config, *, command=...)andload_config(config, *, command=...)take the full command path explicitly, program first. Withoutcommand=(a library call), the root config is ignored and not even read.tangle: the dispatcher builds the path as Cyclopts resolves the command line (program, then groups, then leaf), excluding options and arguments. Each level is canonicalized to its first registered name, so aliases resolve to the real command. The path travels toload_args_or_exit/load_config_or_exitthrough private plumbing incli_helpers, and those helpers pass it ascommand=. Thetangle apischema bootstrap passestangle api <group> <operation>from the leading tokens.normalize_command(path, aliases=COMMAND_ALIASES): collapses whitespace and rewrites alias or deprecated prefixes (longest whole-token prefix, repeated until nothing changes, and bounded). The default table mapstangle-clitotangle, and downstream CLIs pass their own table. Matching is otherwise exact and case-sensitive.Precedence and merge
--config>TANGLE_ROOT_CONFIG>EnvFieldenv tier > default.--configwinning at each leaf._defaults+configs, the order is root <_defaults(still a shallow merge within its file) < entry.nullrulesnullis rejected anywhere, and the error names the key path.--configlayered over an active root entry: anullmapping value means absent, at any depth.annotations: {team: null}removes justteam.nullitems inside lists are values and are kept.--confignullkeeps today's meaning. Scoping it this way keeps the feature opt-in and byte-identical for existing configs.--confignulls in the Tangle CLI or Discovery tangle-deploy configs and tests.base_url: nullcredential isolation, and the default-restoring behavior of typed fields.base_url, paths, direct readersbase_urlis allowed in an entry. It counts as config for credential isolation: ambient environment credentials are not sent to a URL that came from config.args.config_source(name)names the file that supplied a field (top-level-key granularity), so a relative path inherited from the root file resolves against the root file's directory, never the--configdirectory.load_config(config_path, *, command=...)returnsLoadedConfig(.values,.sources) for code that reads config withoutArgsContainer.load.load_config_or_exitand thetangle apipre-parse now use it._load_config_filestill reads exactly one file.config.yamlfiles andTaskEnv.from_configare never layered.TANGLE_ROOT_CONFIGis a no-op.nullis an error naming the variable and the path, never a value.origin()removal (breaking)ArgsContainer.origin()and its_originsbookkeeping are gone. It was flagged provisional in #63 and nothing in Tangle CLI or Discovery calls it. For provenance of file-backed values, useconfig_source().Failure modes
commandskey in a shared file only works for commands you list, and a mistyped key silently applies nothing (mitigated by the near-miss warning).tangle-deployneeds to passcommand=before its commands see root entries (follow-up after the pin).Review focus
null-as-absent handling in_deep_merge, and its scoping to an active root entry.cli._command_identity._envreads per command, and the root-file error prefixing.Tophatting
These are 57 tests on real
ArgsContainer.loadoutputs, covering:_selectat both levels;nullcases;tangle sdk secrets delete;load_config_or_exitand thetangle apipre-parse;cfgpath staying untouched.Mutation-checked: 21 deliberate breaks, plus 4 no-leak and 2 provenance mutations, are all caught.
Checklist
tests/conftest.py:62exists onmaster). ruff clean.git diff --checkclean.uv buildfor both packages. The wheel is 0.1.24 andtest_packagingpasses.pyproject.toml,__init__.py,tests/test_packaging.py, and theuv.lockeditable self-entry (a two-line lock diff;uv locknot run).load_config/config_sourceguidance, and theorigin()mention removed.