Skip to content

Decisions

Why allez is shaped the way it is. Newest first. D-numbers are stable anchors. Source docstrings, the agent skills, the specs and the pipelines cite them, so a number is never renumbered or reused. Decisions that amend or supersede one another share a section; the newest says what holds today, the older ones say what it replaced. Search the page for a number to find it. What is still open lives in plan.md. Version numbers below 0.1.0 in older entries (0.4.0, 0.6.0) belong to the private history before the public release; the public changelog starts at 0.1.0 (D83).

D102 — diagrams on docs pages are styled Markdown lists, not mermaid (amends D70)

The home page was redrawn on 31 Oct 2026, and the five mermaid diagrams left on other pages looked generic beside it: default boxes, and an arrow on the VS Code page that crossed the drawing. Each is now a Markdown list inside <div class="ax-steps" markdown> (numbered steps on a route line, with an ax-who chip for who acts) or <div class="ax-flow" markdown> (cards joined by arrows), styled in docs/assets/stylesheets/allez.css from the theme's own variables, so the palette toggle switches them. The quick-fix diagram repeated the list under it, so the two became one table. D70 kept the workflow page's mermaid for the Azure DevOps wiki and the no-JavaScript fallback; a list does both jobs, because a renderer that ignores the class shows a plain numbered list, and it reads better in plain text than a sequence diagram's source. The mermaid fence stays configured. Lost: GitHub and the wiki show a list where they drew a diagram. Rejected: a theme for mermaid (it still lays out its own boxes); hand-drawn SVG per diagram (a picture to redraw on every wording change). → Workflow

D101 — the sample ships no variable library, and the docs advise against one for a reference (supersedes D96)

D96 added Config.VariableLibrary to allez-reporting to show that a team already using libraries could keep them. It worked, but on 31 Oct 2026 the library was taken out, because it added nothing the sample did not already have. Its four item references and two SQL endpoint values were each rewritten by a parameter.yml rule with a $allez token. Those rules could have rewritten a consuming item directly. And D96's own finding closes the door on the other route: Fabric refuses a library whose reference names an item that does not exist (ReferencedEntityNotFound, 30 Oct 2026). So a library cannot be published into a new environment, or into a long-lived branch workspace that lacks the item, until a rule has written the right id into it. The library is then a second home for an id the rule already holds, plus a value set that is workspace state git does not hold. Gone with it: the library and its six rules, VariableLibrary from allez-reporting's item_types_in_scope, the sample-solution requirement and its two sample tests. The guide's list of mechanisms for a cross-environment reference drops the library and says why, and the agent skill says the same. What stays: allez's handling of a library in a consumer's repo (VariableLibrary in the publish order, the audit's order check and its tests), since a repo may still have one. Lost: the sample's only worked use of the $sqlendpoint and $sqlendpointid $allez attributes; the unit tests still cover them and their docs page is unchanged. The libraries already published into dev and the branch workspaces stay until deleted by hand, because fabric-cicd unpublishes only the types in scope. Rejected: keeping the library as an example of what not to do (an example nobody should copy is not one); a library holding plain values only (a literal per-environment rule on the consuming item does the same with one file fewer). → Which mechanism for a cross-env reference

D100 — query_semanticmodel prints a DAX query's rows; it tests nothing

A hook that runs DAX queries was asked for on 30 Oct 2026. fab_api could already send one, a POST to Power BI's executeQueries, but it keeps no response, so the step said only that the call worked. The new library hook runs one query against a semantic model and prints the first table of the answer in the deploy log: at most 20 rows and a count of the rest, cells cut at 40 characters. It fails only when the query does. A read-back was chosen over a test: a hook that fails the deploy on rows returned, the way dbt tests work, was offered and not taken, and plan.md's static DAX Query View gate is a separate candidate. One query per step, because the API takes one per call and a step's name then says what it reads. The row limit is a constant, not a key. Not proven on this tenant yet: the tenant setting that allows the API, and the call as the pipeline's service principal. Rejected: an example on the fab_api page instead (no output to read); a max_rows key (nobody asked; the count of the rest says when a query is too wide). → query_semanticmodel

D99 — a hook's name spells the item type or format as Fabric writes it (amends D67, D68)

Two library hooks were renamed on 30 Oct 2026, in the same change that broke the hook steps anyway (D97). publish_sql_database took a warehouse too, so its name was wrong for half its work. It is now publish_sqlproj, after the sqlproj definition format it pushes. refresh_semantic_model is now refresh_semanticmodel, after the SemanticModel type a step names in item:. The rule is that a hook's name spells the thing it acts on the way Fabric writes it (<Name>.<Type>, or the format name the API takes), so the name is the search term. run_notebook, run_item and fab_api already follow it. Not taken: an alias for the old names (an unknown action already fails at load and lists the library hooks, which says what to change); renaming run_pipeline to run_datapipeline in this change (not asked; it would follow the same rule). → Hooks

D98 — validate.yml runs only before a merge

The example's validate.yml ran allez audit on every push to feature/* and main, and the guide said to add it as a required Build validation on main too. With that policy set, every push to a branch with an open pull request ran the audit twice: once for the push and once for the policy. The push trigger went on 30 Oct 2026. The pipeline now says trigger: none, because a YAML file with no trigger: key runs on a push to every branch. A pull request into main queues it through the branch policy, and nothing else does. What is lost is a result on a branch before any pull request exists; allez audit on a laptop, or the VS Code extension's Problems panel, gives that sooner. This repo's own gate is unchanged: ci.yml runs on every pull request, and its sample tests fail when the audit is not clean (D90). Rejected: keeping the push trigger and dropping the policy (the policy is what stops a merge; a push run only reports); a path filter (it would still run twice on a change under fabric/). → allez audit · Pipeline setup

D97 — a warehouse's schema is Fabric's definition API, through publish_sqlproj (supersedes D34, amends D68)

D34 sent a warehouse's schema through SqlPackage because Fabric's Warehouse API had no definition operations: getDefinition answered OperationNotSupportedForItem on 21 Sep 2026. Microsoft now documents a Warehouse definition in sqlproj format, the same parts as a SQL database's: the project file, **/*.sql and .sharedqueries/*.sql. A warehouse also takes one pre-deployment and one post-deployment script, each a shared query the project references (in preview). The page was found on 30 Oct 2026. The same day the test tenant served a branch workspace warehouse's definition in that format, and an empty probe warehouse took the sample's Warehouse.Warehouse/ folder through updateDefinition in 47 seconds, with all seven tables, the view and the schema read back afterwards; a second identical push took 33 seconds. The probe used the two Fab methods the SQL database hook already called (D68), on items/<id> because the type has no typed collection in fab.py. So the library hook now takes <Name>.Warehouse as well as <Name>.SQLDatabase, and the type is required: a hook that takes two types should not guess one, so item: Control, accepted since D68, now fails before any fab call. And the sample's allez-data schema.hooks.yml names it. The sample's own .allez/hooks/publish_sql_package.py is deleted: it was the one hook that shelled past fab, and with it go the dotnet build, the SqlPackage install in this repo's fab-setup action and the example's fab-setup-steps.yml, and exclude:, which kept dbt's views out of the dacpac and has nothing to exclude in a folder that holds none. fabric-cicd 1.4.0 still publishes a warehouse as a shell (SHELL_ONLY_PUBLISH), so the hook step stays. The example's fab-deploy-steps.yml ran the deploy in an AzureCLI task only for SqlPackage's login, so that went too: the deploy is a plain script step and the template no longer takes serviceConnection, a break for every consumer pipeline that passes it, taken in the same release as the hook's. fab-setup-steps.yml keeps its AzureCLI task, which is how fab auth login gets the federated token there; this repo's fab-setup action keeps azure/login for the AADSTS code it names when the federated credential does not match. Not proven yet: whether a warehouse's publish refuses a change that would lose data, as a SQL database's does (the sample's warehouse never holds rows, and there was no SQL client to write one); whether a warehouse's deployment scripts run on updateDefinition; and the push as the pipeline's service principal, which the next binding deploy shows. The hook was publish_sql_database until D99 renamed it after the format. Rejected: a second library hook for the warehouse (the same code twice); keeping publish_sql_package as an opt-out (nothing uses it, and a solution that needs SqlPackage can write its own hook). → publish_sqlproj

What the probe showed, 30 Oct 2026, as the pipeline's service principal. The three open questions were run on a throwaway sample/feature/sqlproj-probe branch: four commits, each deployed to dev by dispatching sample-deploy on the branch, so every push was the library hook as the federated principal, a Contributor on allez-data-dev. The warehouse gained a DeployProbe table and two shared queries referenced from the project. The pre-deployment script created a marker table named for the push; the post-deployment script seeded three rows and created its own marker. Marker tables were the evidence because there was still no SQL client: fab ls on the warehouse's Tables/dbo showed which scripts had run, and fab table schema showed the columns. Push A, the first, ran both scripts in dev, and the binding deploy that creating the branch triggers ran them in a brand-new branch warehouse too. Push B, scripts only, ran both again on a warehouse that already held the schema. Push C dropped the populated Extra column and failed the hook with DataLoss: The column [dbo].[DeployProbe].[Extra] is being dropped, data loss might occur, the same guard as a SQL database's (D68). Extra stayed and the post-deployment marker never appeared, but the pre-deployment marker did. Fabric runs the pre-deployment script before it checks the schema change, and a refused publish does not roll the script back. Push D restored the column and added a nullable one, and went green. Each publish took 50 to 65 seconds. Two more facts turned up. Markers from B and C were never in the project, and they survived later pushes, so a warehouse publish does not drop objects the project lacks. And the binding deploy's commit-back kept the hand-written <PreDeploy> and <PostDeploy> references, rewritten with forward slashes in an ItemGroup each; B to D pushed that form back and it worked. Nothing in the hook changed. Its page now says a pre-deployment script must be safe on a publish that then fails, that an object left out of the project stays, and how the marks are written. Still untried: whether a SQL database's publish drops objects the project lacks. The dev warehouse still holds DeployProbe and the seven marker tables, because no later push drops them. → publish_sqlproj

D96 — a variable library is a file parameter.yml rewrites; fabric-cicd activates the set named after the environment

D96 is superseded by D101: the sample no longer ships a library, and the guide advises against one for a reference. What follows is what D96 found, kept because its findings are D101's reasons.

The question on 23 Oct 2026 was whether a team that already uses variable libraries can keep them in an allez repo. The guide had listed a library last of the four ways to carry a cross-environment reference, because an item reference is a raw workspace id and item id that git never rebinds. Nothing in allez had to change. A library is a text item, so a parameter.yml rule rewrites it like any other. The sample's allez-reporting now holds Config.VariableLibrary, with references to allez-data's warehouse and allez-orchestration's SQL database in the default set, variables.json. Four is_regex rules rewrite the four ids with $allez.<sub-folder>.… tokens, each anchored on the variable's name. allez resolves the tokens before the node's fab deploy, and the dag publishes both owners first (D21), so a reference is never published pointing at another environment. The library ships a value set for each environment allez-reporting deploys to, listed in settings.json valueSetsOrder, and the sets override one String only, so every set inherits the rewritten references. fabric-cicd 1.3.0 does the rest: inside one fab deploy it rewrites the library's files, publishes the library, then activates the set named after --env, or Default value set with a warning. The active set is workspace state git does not hold, and this resets it on every deploy. Proven on 26 Oct 2026 by the binding deploy on feature/audit into an empty branch workspace: all four committed ids equal the live ids, and feature is the active set (read back 30 Oct 2026). On 30 Oct 2026 the merge to main published it into dev, with main holding the branch workspace's ids: the four ids were rewritten to dev's and dev became the active set. Test and prod are not yet seen. On 30 Oct 2026 fab import of a library naming an id that does not exist, item or workspace, was refused on create and on update with ReferencedEntityNotFound, so Fabric never holds a reference to nothing, and allez apply, which imports a library without the rules, fails once the workspace its committed ids name is torn down. Two things stay unproven, because the sample does not reach them: whether Fabric accepts a reference to an item the deploying identity cannot read, and whether a lakehouse shortcut that names a variable can be created before its library exists. A reference to an item in the library's own workspace is documented as a second dag node for the folder scoped to VariableLibrary, after the items it names, because fabric-cicd publishes VariableLibrary first and a $items lookup that finds nothing is skipped without failing; that shape is untried. Rejected: publishing the library first with the committed ids and letting a later deploy fix them (a green deploy pointing at another environment, and the prod identity may not read dev's item); value sets holding the references, typed by hand (an empty environment has no ids yet, and every branch shares the one feature set, so a committed id is one branch's id in all of them); a hook that patches the library through fab api after the publish (bespoke over native, D31); a reference in each set file with rules per file (more rules for the same fact). → Variable libraries · Why allez exists

D95 — Dependabot is the only dependency audit; the lock is named so it reads it (amends D87)

audit.yml ran pip-audit over the Python lock and npm audit over the extension's lockfile every Monday. A look at what Dependabot already covered, 27 Oct 2026, found the npm half redundant and the Python half the only thing covering most of the lock, for a reason that was a file name. GitHub's dependency graph had indexed every package in vscode-allez/package-lock.json and had raised the one real alert, the @azure/msal-node advisory, before the workflow did. For Python it had indexed only the fourteen packages pyproject.toml declares, because it does not know a file called requirements-dev.lock; the sixty-odd transitive pins where advisories land (cryptography, requests, urllib3, jinja2, pyjwt, msal) were visible to pip-audit alone. The dependency graph indexes requirements*.txt, so the lock is now requirements-dev.txt, with the same contents and the same uv pip compile command, and the graph indexes every pin in it. Dependabot alerts and security updates are on, so a pin that gains an advisory becomes an alert in the Security tab and a pull request, with no schedule and no workflow. audit.yml is deleted. Python stays out of dependabot.yml's version updates: a version-update pull request would edit the compiled file by hand, and the rule remains to move the group in pyproject.toml and recompile. A security-update pull request edits the file the same way; take the version it names into the group and recompile rather than merge it as is. Rejected: keeping the npm job (a second copy of the same advisory database, failing a run instead of raising an alert); keeping the pip-audit job beside a renamed lock (two reporters of one fact); uv.lock, which Dependabot also reads (D87's reasons stand: uv becomes required and every install line changes); enabling code scanning as a substitute (CodeQL reads this repo's source, not its dependencies' advisories, and is a separate decision). → CONTRIBUTING

D94 — the binding commit is selective when the branch holds what the workspace lacks (amends D46, D91)

The first sample-provision run on the tenant, 27 Oct 2026, committed two branch workspaces and refused the third: allez-data's branch held Scratch.Notebook, which D91 keeps in the repo and excludes with publish.exclude_regex, so the workspace never had it, git status reported it as deleted from the workspace, and the guard D46 put before a mode: All commit refused rather than delete it from the branch. The guard was right and the outcome was wrong: every branch workspace of a solution with an excluded item would never be committed. Fabric's commitToGit takes mode: Selective with the itemIdentifier of each change the status API lists, so when anything would be deleted the commit is now selective: the workspace's added and modified items land, what the workspace lacks stays in the branch, and a dim line names it. With nothing to keep, mode: All as before. A status change without an identifier refuses the commit by name, since Fabric would. Re-running the binding deploy by hand, as the maintainer rather than the pipeline's principal, found the second gap: git credentials are per caller per workspace, the identity that connects a workspace gets them with the connect, and any other caller has source: None, so every git call answered GitCredentialsNotConfigured. The bind now reads the caller's credentials on an already-connected workspace and, when they are not a configured connection, sets them from connection-name before the first git call, so a developer and the principal can take turns on one branch workspace. Both were proven the same day on the smoke branch's workspaces, as the maintainer after the principal: the credentials were set, Fabric accepted the selective commit, the notebook stayed in the branch, and the two workspaces with nothing of their own to commit said so. Also seen on that run: Fabric's git integration writes a SQL database project's files, the pipeline's .platform among them, with CRLF line endings, so a binding commit rewrites every line of files the repo holds as LF; .gitattributes normalises them at the next local commit, and the diff with carriage returns ignored is the real one. Rejected: dropping the excluded notebook from the sample (it is the publish filter's worked example); committing nothing when anything would be deleted (the branch workspace then never records its own ids, D37); file-level selection (an item is the unit git integration syncs). → allez deploy · Troubleshooting

D93 — the Marketplace is published as a managed identity, with no token (amends D83)

D83 had release.yml publish the VSIX with VSCE_PAT, an Azure DevOps personal access token in the marketplace environment, because the Marketplace had no trusted publishing. On 27 Oct 2026, with the evaluationcontext publisher made and the token still to create, Microsoft's publishing guide said Azure DevOps PATs retire on 1 December 2026 and recommended Entra ID instead. The marketplace job now signs the Azure CLI in with its OIDC token through azure/login, as a user-assigned managed identity whose federated credential trusts this repository's marketplace environment, and runs vsce publish --azure-credential, which @vscode/vsce 4 has; the identity is a member of the publisher with the Contributor role, added by its Visual Studio profile id, which a one-off marketplace-identity.yml prints by signing in as the identity and reading the profile endpoint, because the Marketplace knows none of the identity's Entra ids. The client id and the tenant id are variables, not secrets, as on the sample's environments; nothing on GitHub holds a credential any more, which is where PyPI already was. A managed identity rather than the sample's shape, an app registration with a federated credential: the only published GitHub Actions run of this route reports that an app registration signs in but is refused at publish, and the identity costs nothing beyond a resource in the Fabric group. Rejected: a PAT for the first release and the identity afterwards (two setups five weeks apart, for a token Microsoft is withdrawing); vsce publish --oidc, the Marketplace's own trusted publishing (in vsce's main branch, not in the 4.0.0 release, and reported unsupported by the backend; plan.md watches it, since it would remove the identity too). → Releasing

D92 — the rig has two environments, a branch workspace and dev; the gates, the plans before them and the deployed/<env> tags are the example's (amends D90, D66)

D90 gave the rig a solution repo's whole promotion: dev, the test plan, test behind the fabric-test reviewers, the prod plan, prod behind fabric-prod, and a deployed/<env> tag moved after each green deploy so the next plan could list what would change (D66). On 27 Oct 2026, with the first runs on the tenant, that was cut to what tests the CLI: sample-provision.yml makes and fills a branch workspace per sample/feature/* branch, and sample-deploy.yml deploys dev on every push to sample/main, then sweeps orphans. Two environments exercise every verb and hook, the overlay revisit and the $allez tokens; a second and a third deploy of the same commit behind an approval the one maintainer would click themselves proved nothing more, cost six workspaces and two gates GitHub refuses to put reviewers on while the repo is private, and the tags needed contents: write on a job that otherwise only reads. The flow a team runs, with the gates, the plan before each and the tags, stays in allez-example's deploy.yml, where D63 and D66 describe it; --since and the tag are still the CLI's and the plan's, exercised by the unit tests and the example. fab-plan left the repo with the plan jobs; fab-deploy lost its record-deployment input, the tag lookup and the tag push; the dev job holds contents: read and keeps its token only for teardown --orphans, which lists the private remote's branches. The fabric-test and fabric-prod environments, their federated credentials and the six test and prod workspaces are referenced by nothing and can go, or stay for the day the rig grows a gate. The sample's own config.yml and parameter.yml keep their five environments: the sample is also the worked example, and the audit checks every one. Rejected: keeping test as a second deploy of the same commit (the same deploy with a wait in front); dropping sample/feature/* instead (the branch workspace is the half of the CLI dev never touches); a deployed/dev tag with no plan that reads it (a tag nothing reads). → GitHub Actions · Sample

D91 — which items a node publishes is answered once, mirroring fabric-cicd 1.3.0 key for key; the plan, the document, the view and the audit take that answer (amends D59, D77, D84; amended by D94)

allez decided which items a node deploys from one key, core.item_types_in_scope, read as a plain list. fabric-cicd 1.3.0 decides it from nine: that key, which may be a mapping by environment, core.repository_directory, and the publish and unpublish filters. A solution that used any of them got a plan, a document, a Deployment view and an audit that said an item deploys when fab deploy would skip it, and a per-environment item_types_in_scope mapping was read as a list of environment names, so every item was out of scope. The plan is what an approver reads (D59) and the audit's point is to check the repo before a deploy (D75), so on 27 Oct 2026 both were made to answer the question fabric-cicd answers. One module, scope.py, holds the rule, read from the installed package rather than its docs page, which does not say what the regexes match: every setting of a node's effective config (D59) is read per environment as get_config_value reads it, a mapping yielding its entry or nothing; an item is published when its type is in scope, publish.items_to_include is unset or lists it, its name does not re.match publish.exclude_regex, no ancestor of its folder path re.searches publish.folder_exclude_regex, and publish.folder_path_to_include is unset or lists its folder path, the folder filters sparing a top-level item and the first failing filter being the reason; an orphan is unpublished when its type is in scope, unpublish.skip is off, and unpublish.items_to_include lists it when set, else unpublish.exclude_regex does not match it. Items are discovered under the environment's core.repository_directory, resolved against the config.yml folder. The three states fabric-cicd refuses at deploy (a filter without its features: flag, both folder filters for one environment, a regex that does not compile) are refused at load, naming the file, the key and the fix, before any fab call. Every reader takes that answer: deploy --plan prints Item types: resolved for the environment and an Excluded: line naming each item a filter excludes and the key; --since lists only published items and judges a removal with every unpublish rule; the document marks each item in_scope as before and adds excluded_by, so the view's hover card and the CodeLens count follow it without computing anything (D77); the audit scans the files of the items some visit publishes, filters included, while the order check keeps its type-level reasoning, since an item excluded by name still belongs to its type's pass (D84). fab deploy still does the filtering; allez only predicts it, the way effective() mirrors _merge_overrides and PUBLISH_ORDER mirrors SERIAL_ITEM_PUBLISH_ORDER, and the module header names the version to re-read on an ms-fabric-cli upgrade. publish.shortcut_exclude_regex filters shortcuts inside a lakehouse, not items, so the plan prints it and nothing else changes. No allez.yml key, flag or verb was added. The sample gained one filter to show it: allez-data keeps a Scratch.Notebook committed for reference and excludes it with publish.exclude_regex: "^Scratch". Rejected: validating config.yml against fabric-cicd's full schema (allez would re-implement a validator fabric-cicd runs on every deploy; only the three refusals that change the plan's answer are mirrored); modelling shortcut_exclude_regex per shortcut (a shortcut is not an item, the view draws items, and the audit's ids live in item files either way); a --params-style override to try a filter without committing it (declined in plan.md already, a repo file says it reproducibly); reading features: to warn about experimental flags in general (fabric-cicd prints that warning itself at deploy time); dropping the type-level scope the token resolver uses (D84 orders tokens by the type a visit publishes, and an item excluded by name still belongs to that type's pass); a document field per orphan (orphans are workspace state, which the document never reads; --since is where a removal is judged, D66). → config.yml · Reading the plan · allez audit · The deployment dag

D90 — the sample is this repo's test rig on GitHub Actions; the Azure Pipelines example is allez-example (amends D83, D63, D47, D32; amended by D92)

D83 kept the CLI, the sample, its three Azure Pipelines and GitVersion.yml in one repository, with the pipelines queued by hand in an Azure DevOps project that read this repo. On 26 Oct 2026, a day later, the costs were plain. Nothing in this repo ran the sample against a tenant: ci.yml fakes fab, nobody queued the pipelines for a pull request, and D84 records that the pipeline, the lakehouse and the environment had never been deployed from here. The sample carried EDIT placeholders for every principal, the capacity, the connection name and fifteen connection ids, so it could not have been. GitVersion.yml existed for a consumer's version and constrained this repo's release tags. And the example a team copied was tangled with the testing: an "in this repo" paragraph in every pipeline header, an install-from-checkout branch in every template, trigger: none with the real trigger in a comment.

Now the two jobs are in two repositories. This repo keeps fabric/ as the CLI's test rig, with the test tenant's real values (object ids, a capacity name, connection ids; none a secret), deployed by two GitHub Actions workflows that install allez from the checkout: sample-provision.yml when a sample/feature/* branch is created, which GitHub's create event fires once per branch and Azure DevOps could not (D32), and sample-deploy.yml on every push to sample/main, dev then the test plan, test behind the fabric-test reviewers, the prod plan, prod behind fabric-prod. sample/main is only ever fast-forwarded from main, so a deploy tests a commit on main without queueing an approval for every CLI merge. The login is GitHub's OIDC token handed to fab auth login --federated-token, with azure/login beside it for SqlPackage; the client and tenant ids are environment variables. The deployed/<env> tags stay (D66); no version is computed and nothing is tagged. The root allez.yml lists sample/main under protected-branches; nothing in the CLI changed. .azure-pipelines/ and GitVersion.yml left this repo. A second repository, allez-example, is the example a team starts from: fabric/ with placeholders, .allez/hooks/, the three pipelines in consuming shape (allezVersion: '0.1.0', the main trigger live, tagRelease on, templates that install allez==<version> and nothing else), GitVersion.yml and a README that names the allez tag it was copied at. The two copies of fabric/ may diverge and no test ties them. D63's version and tag are the example's only. The one gap: output.py speaks Azure Pipelines logging commands and nothing on GitHub Actions, so a warning in a sample run reaches the log, not the run summary (plan.md). Nothing had run on a tenant when this was written; the first runs are the proof.

What the first run showed, 27 Oct 2026, a local allez deploy fabric --env dev as the maintainer with the workflows not yet run. Fabric checks a pipeline's connection ids at publish time, not at run time: with placeholder ids the publish failed with "User does not have access to the connection used in the Pipeline", so a first deploy needs its connections before its last node. A notebook activity takes only a Notebook connection, whose credential is a service principal or a workspace identity and never a user's sign-in, so the notebook activity now carries no connection and runs as the identity that published the pipeline, and the sample's connection rules went from three to two (amends D84). The FabricSql and PowerBIDatasets connection types carry no server, so one connection of each serves every environment on the test tenant and each rule names the same id five times; a solution whose connections differ per environment names each. fabric-cicd logs every deferred rule at the first node as [error] Item type 'DataPipeline' not in scope and carries on. publish_sql_database, SqlPackage, the notebook run, the takeover (which fails and continues on an empty workspace, as designed), the refresh and the pipeline's three activities all passed. And a deploy from a Windows checkout rewrote every LF parameter file as CRLF, because tokens.py read with read_text and restored with write_text; it now round-trips bytes. The first sample-deploy run, the same day, failed at the login: a repository created after 15 July 2026 presents GitHub's immutable OIDC subject, repo:EvaluationContext@165394302/allez@1433638537:environment:<env>, with the owner's and the repository's ids, and the credentials carried the id-less form the portal's scenario writes, which Entra refuses with AADSTS700213. fab auth login reported only its generic line, so fab-setup now runs azure/login first, which names the code, prints the token's claims, and turns fab's debug output on for the login alone. With the login green, the runner's floating versions (ms-fabric-cli 1.8.0, fabric-cicd 1.4.0, against the lock's 1.7.0 and 1.3.0) found the next gap: 1.4.0 validates every $-prefixed replace value before deploying, so the first node failed on the pipeline rules' deferred $allez tokens that 1.3.0 had skipped by filter. A deferred token is now written as allez-deferred:<token> for that publish and restored after. 1.4.0's serial publish order inserts CosmosDBDatabase after SQLDatabase and appends GraphModel, with the rest in the same order, so PUBLISH_ORDER still sorts every type the sample has; the lock moves to 1.4.0 when the copy is refreshed.

Rejected: fabric/ in the example only, with a small fixture here (the three-workspace sample is what finds a regression; a fixture is a second solution to keep honest); deploying on every push to main (an approval per CLI merge trains rubber-stamping, D63); provision by workflow_dispatch (the hand step was Azure DevOps's gap); filling the placeholders at run time from GitHub variables (two shapes of one sample, for values that are not secrets); testing allez-example from this repo's CI (couples the two again; its own validate.yml and deploy pipeline are its gates); a version in the GitHub rig (nothing reads one); an Azure Pipelines copy kept here too (two rigs for one sample); a git ref install in the example until 0.1.0 is on PyPI (a line no team should copy). → GitHub Actions · Pipeline setup · allez-example · CONTRIBUTING

D88 — pyproject.toml holds the dev pins and reads the version from the package (amends D87)

Three things fabric-cicd's pyproject.toml does that allez's did not, taken on 26 Oct 2026. The version was written in three places, pyproject.toml, src/allez/__init__.py and the extension's package.json, and release.yml existed partly to refuse a tag that disagreed with any of them; now pyproject.toml declares dynamic = ["version"] and hatchling reads it from __init__.py, so there are two copies and the check compares two. The contributor pins lived in a requirements-dev.in beside the lock; now they are the dev group under [dependency-groups] in pyproject.toml, where Python packaging puts them, and uv pip compile pyproject.toml --group dev writes the same requirements-dev.lock, which pip still installs, so uv is still needed only to recompile. requires-python gained the ceiling the classifiers already implied, >=3.10,<3.15. Not taken: their 3.13 and 3.14 classifiers (untested here); ranges in the dev group (the pins are exact so CI and PUBLISH_ORDER agree on a fabric-cicd version); uv.lock and uv sync (D87). → CONTRIBUTING

D87 — dependency handling matches fabric-cicd's: a pinned install, one plain lock here, no lock in a solution repo (supersedes D82, amends D73; amended by D88, D95)

D82 set a bar above the tool allez wraps. fabric-cicd declares ranges in pyproject.toml, keeps one uv.lock for its own CI and tells users to pip install fabric-cicd. allez asked every solution repo to compile a hashed requirements-deploy.lock to pin one version, and to recompile it at every upgrade, for a protection (a file added to an existing PyPI version by a hijacked account) that covers nobody who ever upgrades, since the same account can publish a new version. Now a solution repo sets allezVersion in variables.yml and the step templates run pip install allez==<version>; in this repo the variable is empty and the templates install from the checkout. ms-fabric-cli is bounded to one major in pyproject.toml, so its version and fabric-cicd's float within it and are printed at the top of every job's log. This repo keeps one plain lock, requirements-dev.lock, for its own development and CI, compiled without hashes; it still pins fabric-cicd, and a sample test holds that pin to the version PUBLISH_ORDER in plan.py was copied from. Gone: requirements-deploy.in and .lock, --require-hashes, the --no-deps --no-build-isolation --no-index build, locks.yml, the lock-agreement test, the two-weeks rule and the allezSource template parameter. D73's pins that are not Python packaging stay: Python, SqlPackage, GitVersion.Tool and the ubuntu-24.04 image. Rejected: keeping the hashed lock for solution repos only (the cost was theirs); uv.lock and uv sync here (uv becomes a required tool and every install line changes, for the same standard); pinning ms-fabric-cli in the install line (two versions to move, for a guarantee fabric-cicd does not give its own users); a requirements.txt in the solution repo (a second place for one fact); keeping locks.yml (it recompiled two locks that no longer exist). → Install · Pipeline setup · Deployment versions · CONTRIBUTING

D86 — ms-fabric-cli is a dependency of the package (amends D83)

pip install allez left fab out. The package declared typer, PyYAML and rich, and every install page said to install ms-fabric-cli beside it, because allez imports neither fab nor fabric-cicd and only shells out to fab. That reasoning described how allez calls fab, not whether it can run without it, and it cannot: every live run fails at its first fab call, and only --plan, audit and document are offline. On 26 Oct 2026 ms-fabric-cli>=1.7.0 joined the dependencies, so one install brings fab, and fabric-cicd with it. The pipelines are unchanged: they install the hashed requirements-deploy.lock, which already pinned both, and build allez with --no-deps. The lower bound matches the version the lock pins and the plan's copied merge rule was read from (D59); the lock, not the package, says which exact version a deploy runs. Rejected: pinning fabric-cicd too (it is ms-fabric-cli's dependency, and allez never imports it); an exact pin in pyproject.toml (a laptop would then need a new allez release for every fab fix; the pipelines pin through the lock). → Install

D85 — the tool is allez: the shout from the mat, with a mark and a vocabulary from bouldering

The tool was called stitch until 25 Oct 2026. That name sat beside Stitch Data, Qlik's data integration product, and the first PyPI name put two of Qlik's words together (stitch-fabric, next to Talend Data Fabric). It was renamed before the first push, while a rename cost an afternoon. Allez is what climbers shout at someone on the wall, and the tool does the same for a deploy. Everything that carried the old name changed with it: the package and the command allez (the plain name was free on PyPI, so no suffix), the .allez/ folder, allez.yml and allez.schema.json, the $allez.* token prefix, the extension evaluationcontext.vscode-allez, the sample's allez-* workspace names and the repository. Older entries in this log now read with the new name. The mark is a boulder problem drawn as a dag: holds as nodes, moves as depends_on, the lip of the boulder as prod. The docs take their coined names from bouldering, defined once on the home page: the dag is the circuit, the plan is the topo, a feature branch is a project, the approver is the spotter, a failed node is a dab, the deployed/<env> tags are the ticklist, a first deploy into empty workspaces is a sit start, a deploy that goes green first time is a flash, a green deploy to prod is a top-out. Running text still says the concrete thing, as the voice rules require; the climbing word is for a heading and the sentence that defines it. Considered and set aside: keeping stitch with a distinct mark (the word was the exposure, not the drawing); a -fabric suffix on any name (Fabric is Microsoft's mark); dag names (dagwalk, topout), brand names (treatas, ctxshift), Zure's lines and Fabric's codename Trident (registered in the software class by others); flash (Adobe's, with three other meanings in software) and venga (software neighbours in CRM). Allez is a common French word, so it is held by being first in its field rather than by registration; a trademark search in the UK and EU registries is still the step before the first tag. → Home · NOTICE

D84 — a revisited sub-folder resolves its tokens at the visit that publishes them; the sample grew a pipeline, a lakehouse and an environment to prove it (amended by D90, D91)

Amended by D90's first run: the notebook activity carries no connection, because Fabric's notebook connector takes only a service principal or a workspace identity, so two key_value_replace rules remain; and the sample has now deployed from this repo.

On 25 Oct 2026 the sample gained more of what a solution throws at the CLI: a DataPipeline in allez-orchestration that reads its settings from the control database, runs allez-data's notebook and refreshes allez-reporting's model; a Lakehouse with a shortcut to the shared calendar and an Environment inside a Fabric folder in allez-data, both attached to the notebook; two String variables carrying the lakehouse's SQL endpoint host and id; three key_value_replace rules for the activities' connection ids. The pipeline names items two other workspaces publish, so allez-orchestration is visited twice: config.yml publishes the database first of everything and an overlay publishes the pipeline last, orchestration → data → reporting → orchestration. That revisit, which the dag page had described since D16, did not work. allez deploy resolved every $allez.* token in a sub-folder's parameter files at each visit, so the pipeline's tokens failed at the first node on an empty tenant; the audit judged every token at the sub-folder's first visit and scanned the sub-folder with config.yml's scope alone, so the pipeline's ids were never checked and its rules read as dead. Now a rule targets item types: its item_type, else the item folder its file_path names, else the section's own type (spark_pool an Environment's, semantic_model_binding a semantic model's), else every type. allez deploy resolves a node's tokens only for the rules in that node's effective scope and prints the rest as deferred; allez document lists a token under the node that resolves it; the audit judges a rule or token at the first visit whose scope includes its type, and scans the union of every visit's scope. A sub-folder the dag visits once sees no change. The pipeline's activity shapes are copied from pipelines Fabric exported into public repositories (a notebook and a refresh activity, with their externalReferences.connection; a Lookup over a Fabric SQL database), and the lakehouse, environment and pipeline files from fabric-cicd's sample workspace; none has been deployed from this repo yet, and the connection ids in allez-orchestration/parameter.yml are placeholders marked EDIT. Rejected: a second parameter file named by the overlay's core.parameter (two files for one sub-folder, and the audit and the extension would need to know which applies at which node); a fourth sub-folder for the pipeline (three more workspaces per environment, for a pipeline whose point is to read the control database beside it); resolving every token at the sub-folder's last visit (the first visit's own rules would see unresolved tokens); a node-level environments: on the smoke run (the hook step's own environments: says where the pipeline runs). → The deployment dag · $allez.* variables · allez audit · Sample

D83 — allez is public: one repo on GitHub, CI on GitHub Actions, the sample's pipelines on Azure Pipelines, releases on PyPI and the Marketplace (amended by D86, D90, D93)

Amended by D90: the sample's Azure Pipelines and GitVersion.yml live in allez-example, this repo deploys its sample from GitHub Actions with the test tenant's values in the tree, and the v prefix rule on release tags has no reason left. Three things this entry rejected, two repositories, GitHub Actions for the sample's deployment and tenant values in the tree, are now the shape.

allez was an internal tool in one Azure DevOps project. A checkout linked to a private wiki, installed from a private git URL, and its sample carried the maintainers' own principals, capacity and git connection. On 25 Oct 2026 it moved to github.com/EvaluationContext/allez as one repository with one initial commit; the private history stays private, and the version restarts at 0.1.0 with the old changelog folded into one entry (changelog). What changed with the move:

  • CI is GitHub Actions. .github/workflows/ci.yml replaces .azure-pipelines/allez/ci.yml (D60): ruff, both pytest suites under coverage with a floor of 88%, zensical build --strict, a package build checked with twine, zizmor over the workflows, and the extension's tests and VSIX, on every push to main and every pull request. Every action is pinned by commit and Dependabot moves the pins; audit.yml runs pip-audit and npm audit weekly; locks.yml opens the monthly lock refresh. It needs no secret, so a pull request from a fork runs it in full. docs.yml publishes the site to GitHub Pages and replaces the wiki pipeline.
  • A tag is a release. release.yml runs on a <major>.<minor>.<patch> tag, refuses one that disagrees with pyproject.toml, src/allez/__init__.py or vscode-allez/package.json, and publishes the wheel and sdist to PyPI through trusted publishing (no stored token), the VSIX to the VS Code Marketplace (a token in a protected environment; the Marketplace has no trusted publishing), and both files on a GitHub release. The extension's version is allez's, so the two never diverge (amends D77, which rejected the Marketplace for an internal tool).
  • The package was stitch-fabric. stitch was taken on PyPI, so the first name carried a suffix; D85 renamed the tool and the package is allez, as are the import and the command. build and twine join the contributor lock so CI and the release build the same way.
  • The sample's three deployment pipelines stay on Azure Pipelines (D63), reading this repo from GitHub. Their approvals and environments are Azure DevOps features every consumer already runs on, and they are proven there; a consumer on GitHub Actions runs the same six commands from its own workflow. The sample's git: block names this repo on GitHub, since that is where the branches live, and every principal, the capacity, the tag and the connection name are placeholders marked EDIT.
  • A consumer pins the release; the consumer copies are gone (amends D38, D47). A laptop installs allez==<version>. A solution repo copies .azure-pipelines/fabric/ whole and compiles its own hashed requirements-deploy.lock naming that release; the step templates build allez from allezSource only when a pyproject.toml is there, so one set of pipelines serves this repo and every copy, and there is no second copy to keep in step. In this repo deploy.yml has no trigger: it is queued by hand on any branch to test allez, so testing never needs a merge to main. The main trigger a solution repo uses is written beside it, and tagRelease turns on the prod tag.

Rejected: two repositories, CLI and example, which the first proposal planned (the sample tests run against the CLI checkout beside them and would otherwise need a published release to test against; one repo at one version is what every consumer pins, and the second repo was a second place to keep in step); publishing the private history (its sample held real principals); stitch as the PyPI name (taken; the name itself went with D85); a v prefix on release tags (GitVersion.yml would count them as the sample's version); GitHub Actions for the sample's deployment (its gates live where the consumers do); a repository resource pointing at this repo from a solution repo's pipelines (it needs a GitHub service connection or a mirror; one lock line needs neither); separate repos for the CLI, the extension and the example (one version, one tag, one changelog is simpler to release than three, and the sample is the CLI's integration test). → Install · Use from another repo · CONTRIBUTING

D82 — the locks are hashed, allez builds offline, and contributors install a lock (amends D73; superseded by D87)

A dependency review on 24 Oct 2026 found that D73 locked versions but not files. PyPI lets a maintainer add a file to an existing version, and the lock recorded no hashes, so pip would install a wheel added to a locked version by a hijacked account. Every deploy job also built allez with pip install --no-deps <checkout>, which downloaded the newest hatchling and its dependencies into the job that logs in as the service principal a minute later. CI installed .[dev], so pytest, ruff and jsonschema came in at whatever version PyPI served that day, in the job that builds the VSIX. Now requirements-deploy.lock is compiled with --generate-hashes and includes hatchling; the step templates install it with --require-hashes and build allez with --no-deps --no-build-isolation --no-index. Contributors and CI install requirements-dev.lock, compiled against the deployment lock so the two cannot disagree, and a test checks both. CI installs the extension with npm ci --ignore-scripts. A version under two weeks old is held back with -P when a lock is regenerated. The dev container went too: it installed its tools without versions, and it was a second copy of the setup that CONTRIBUTING.md now describes. The extension lost its plan and audit buttons at the same time, and starts allez document without a shell, after an environment name in allez.yml was shown to run as a command through it. Rejected: uv pip compile --exclude-newer (it moved existing pins backwards); an npm override forcing @azure/msal-node 7 past @azure/identity's range (days-old releases, in code vsce runs only when publishing); pip install git+…@<tag> for laptops (it resolves dependencies from PyPI at install time); keeping a pinned dev container. → Install · Deployment versions

D81 — every id reference has a coverage, and the view draws them per environment

The view now draws references coloured by how each is covered: not covered, autobinding, dynamic replace, static replace, $allez token (24 Oct 2026). The audit already found every id and which rules rewrite it; it now keeps them, and allez document classifies each one for its environment in fabric-cicd's own order: a logical id of an item in the sub-folder is replaced first, then the rules, then a nil workspace id is rebound. So autobinding means exactly what the pinned fabric-cicd 1.3.0 does by itself, read from its source: _replace_logical_ids, _replace_workspace_ids on workspaceId, workspace and default_lakehouse_workspace_id, and a lakehouse shortcut's nil itemId. Nothing else is called autobinding, which is what plan.md asked before any view claimed it. A reference covered by a rule takes the kind of the rule's replacement for the environment; a rule with no value there leaves it uncovered. A target is resolved where the solution has one, a $workspace.<name> across every folder and environment so a cross-environment read says which. Static and uncovered ids have none, so the view marks them instead of drawing to nowhere. The view draws one band per environment, foldable, with shared columns, and the arrows only for the active file by default: with every item's arrows on, a five-folder solution is unreadable. Rejected: drawing an arrow per id (fifty from one folder); an "outside the solution" box for static and uncovered ids; guessing a literal's target. → allez document

D80 — a finding that is the design is accepted in the sub-folder's allez.yml, with a reason

A consumer solution reads prod's landing files from feature and dev on purpose, and the audit warned order on it in both environments on every run, with no way to quiet it: --ignore takes ids only, records nothing in the repo and cannot be passed by the extension. A sub-folder allez.yml key was chosen over a comment beside the rule and over leaving the warnings (24 Oct 2026). audit.accept: is a list of findings by kind, value as the audit prints it, optional file and environments, and a required reason. Every verb validates it at load. An accepted finding is still printed, dimmed, with its reason, so nothing is hidden; it just stops counting, failing and annotating. An entry that no longer matches anything warns unused-accept, failing --strict like a dead rule, so the list stays honest. Rejected: a comment directive (it can't reach an id in a JSON item file, and hides a language in comments); a wider --ignore (a flag the extension can't pass and no file records); the root file (findings belong to a sub-folder's files); silent acceptance. A consuming repo needs a allez tag that has this before adding the key: an older one refuses audit as an unknown key. → allez audit

D79 — the audit: logical ids are fabric-cicd's, workspace references wait on no publish

A consumer solution showed the audit wrong in two ways (24 Oct 2026). A pipeline that names a notebook or SQL database in its own sub-folder by the item's .platform logical id was reported uncovered, but fabric-cicd replaces every logical id it finds in the repository directory with the deployed id (_replace_logical_ids), whatever the scope. Now any GUID equal to the logical id of an item in the same sub-folder is skipped as bookkeeping; one from another sub-folder is not, since that is not fabric-cicd's repository directory. And the order check warned on $allez.<sub-folder>.$workspace.$id and $name, and on $workspace.<name>.$id, as if a workspace were published. It isn't: a named workspace is infrastructure, a branch one is made by allez provision, and a missing one fails the deploy loudly. Only $items references wait on a publish, so only they are checked. Rejected: dropping the warning for $workspace.<name>.$items… naming a workspace outside the run (an intended read of another environment's data looks the same as a typo'd name, which fabric-cicd skips and publishes green); skipping connection ids (whether environments share one is the solution's fact). → allez audit

D78 — an item inside a Fabric folder is an item

A Fabric workspace can hold folders, git integration writes an item inside the folder it sits in, and fabric-cicd publishes the folders with the items. allez read only a workspace sub-folder's direct children, so a consumer solution whose items live in folders showed half its items in the deployment view, scanned half of them in the audit, and listed a changed nested item as a changed config file in deploy --since (seen 23 Oct 2026). Now discover_items walks plain sub-folders to any depth and returns every <Name>.<Type>/ folder; it never enters an item (its own sub-folders are definition parts) or a dot folder, and the solution root is still judged by its direct children, so it never passes as a workspace folder. The document gives each item its folder, and the view groups items under it. The audit's item_type and item_name filters take the item from the first <Name>.<Type> segment under the sub-folder, where they read the first segment and so called every rule on a nested item dead; file_path stays relative to the sub-folder, folder included, as fabric-cicd resolves it. --since names a nested item by its path from the sub-folder. One thing is not done: apply imports a nested item at the workspace root, because fab import into a .Folder path is undocumented; it is in plan.md to prove first. Rejected: failing on a nested item (git integration decides the layout); reading a nested folder as a second workspace sub-folder (it has no config.yml and deploys with its workspace). → Repo structure

D77 — the VS Code extension replaces allez ui; allez document is the bridge (supersedes D74, D76; amended by D83, D91)

The page grew into an editor: D74 gave it file saves, D76 gave it Monaco, schema completion, save conflicts and themes, all things VS Code already has. The same day D76 shipped, the direction was set: code first, VS Code when a GUI is wanted, one GUI only. So the page came back out, Monaco bundle, frontend/ build, Playwright suite, CI rebuild-diff step and all, before any of it reached a release.

What replaced it. allez document [PATH] [--env <name>] prints the page's old data to stdout: the plan's document (D59) plus the audit's findings (D75) for every declared environment and each rule's matched lines. The verb count stays at six. vscode-allez/, at the repo root, is the GUI: findings as Problems entries at their file and line (an unlisted config.yml warns too), three quick fixes as previewed text insertions (a find_replace skeleton for an uncovered id, a missing environment under a rule's replace_value, a dag node for an unlisted config), and one deployment view in the panel area, beside Terminal and Problems, that follows the active editor: the root allez.yml shows the dag as folder containers mirroring each sub-folder's allez files in five sections (config, parameters, pre hooks, items in publish order, post hooks) with the root allez.yml above and a subdued display-only compare environment, a parameter file its rules with matched files and lines, a config file its node, a hooks file its steps, an item file the rules and findings that touch it, with a file list beside the dag for fast navigation and every element opening the real file. A CodeLens above each rule says how many files it matches, dead rule, and the environments it lacks; one above each dag node says what the node deploys and where. A first cut as a draggable editor-tab webview went blank when moved between tab groups, twice, so the view moved to the panel area where the workbench owns the lifecycle. The extension computes nothing about a deploy: every fact comes from allez document, so the CLI and the GUI cannot disagree. allez.yml gets completion through the YAML extension and the packaged copy of allez.schema.json, copied from the repo root at build, never forked (D48).

The extension is plain JavaScript with no bundler, the style the page was written in; @vscode/vsce is the only build tool and node --test runs the pure logic's tests. CI builds the VSIX from the pinned lockfile and publishes it as an artifact; it is not on the marketplace. Nothing requires the extension: the CLI is complete without it, and one allez.command setting locates allez when it lives in a venv.

Rejected: keeping the page and the extension both, because every feature would exist twice and drift; a read-only page, still a server and assets for an audience that has VS Code; importing allez's Python modules from the extension, because the CLI is the public surface; a long-running server for the extension, because VS Code watches files itself; deployment logic in TypeScript, because plan.py and audit.py are the one engine; marketplace publication, for an internal tool. → allez document

Amended 24 Oct 2026. The view's compare of two environments is gone. It dimmed what was the same and marked what differed, display only, and in use it added little the bands and hover cards did not already show. One chip per container hides it in every band instead, for reading one part of the deployment.

D76 — the ui's editor is Monaco, bundled and committed (amends D74; superseded by D77)

D74 rejected a code editor library and a build step; both were accepted on 23 Oct 2026, a full editor over the smaller highlight-and-validate option that was offered. The page now opens files in Monaco with monaco-yaml: syntax colours for YAML, JSON, Python, SQL, Markdown and XML, and completion, hover help and shape diagnostics on allez.yml from the repo's allez.schema.json. The page also takes the documentation site's look: its header and light/dark palettes, a theme toggle the browser remembers, the allez mark, and the portal's icon beside each known Fabric item type, reusing the SVGs under overrides/.icons/.

The line that holds: an installed allez stays Python-only and offline. frontend/ holds the pinned npm manifest, lockfile and esbuild script; its output under src/allez/ui/assets/ is committed, ships in the wheel and sdist, and CI rebuilds and diffs it so it cannot go stale. Node is needed only to change the frontend. Nothing is fetched at run time: the editor, its workers, the schema and the icons are package files, a # yaml-language-server: $schema= comment resolves to the shipped schema whatever URI it names, and the Playwright suite fails if any request leaves 127.0.0.1. Editor diagnostics are advice: a save is never blocked by one, the loader stays the authority (D74's save contract is unchanged), and item files stay read-only (D9). Two bugs surfaced on the way: the yaml worker needs a process shim or a relative schema comment kills all validation, and mtime now travels as a string because st_mtime_ns exceeds JavaScript's safe integer range, so every browser save conflicted with itself.

Rejected: CDN assets or a remote schema catalog; Node as an install prerequisite; a frontend framework; blocking an invalid save; theme or editor keys in allez.yml. → removed by D77

D75 — allez audit is a static coverage check and an order check, rebuilt on the plan's data

The first build (September 2026) was taken out with the backfill it depended on; the proposal said yes again on 21 Oct 2026 and it is rebuilt on plan.py. Four findings: uncovered and env-gap fail; dead-rule warns, or fails with --strict; order warns. The environments come from the root file (D19), rules from the parameter file and its extend: templates (D45), and --env narrows to one. Two things the first build got wrong are fixed: a regex rule is matched over the whole file with the capture group as the id, the way fabric-cicd applies it, so the sample's anchored rules count, and the nil GUID in a project file is bookkeeping. key_value_replace stays the one approximation, the JSONPath's final key on the id's line. The order check assumes empty workspaces and uses a copy of fabric-cicd's serial publish order stamped with the pinned version; a $items reference to a type that publishes later or in the same pass, a $allez.* token naming its own or a later node, and a $workspace.<name> nobody deploys earlier each warn, because fabric-cicd skips a lookup that finds nothing and publishes green. The findings and each rule's matched lines ride in the document allez document prints (D77). On an agent every finding is a logissue annotation with file and line, through the one output module, so the old --annotate flags are gone. The sample gains validate.yml, which runs the audit on every feature push with no service connection, for a required Build validation on main; the pull request template keeps its parameter.yml prompt, because the check is static. Rejected: reading from a tenant; a JSONPath dependency; a --json output; failing on order. → allez audit

D74 — allez ui is a page over the plan's data, and may save allez's files (amended by D76; superseded by D77)

The only picture of a deploy before it ran was the text plan, and the relationships between the dag, the workspaces, the items, the hooks and the rules lived in the reader's head. allez ui serves a local page: the dag drawn with each workspace as a container and its items inside in fabric-cicd's publish order, hook steps above and below, a step-through of the deploy, every parameter.yml rule with its replacement for the chosen environment, and the solution's files. The data the page shows is built by plan.py, the module the text plan now prints from, so the two cannot drift, and a golden test pins the printed plan. Standard library only: http.server on 127.0.0.1 on a port the operating system picks, one HTML file, one script and one stylesheet in the package, SVG drawn by hand, no framework and no build step, as the docs player (D70). No fab call: ids are the cache's or placeholders, as in --plan.

Editing on the page was pushed back on, as a second editor with its own way of breaking a YAML file, and kept so that a team arriving from Fabric's portal features can adopt fabric-cicd and allez from a page. It is scoped to allez's own files; item folders are read-only, because they come from Fabric through git integration (D9). A save sends the whole text and the mtime the page read, is refused when the file changed on disk, is written beside the target and renamed over it, and is answered with the re-read document or the loader's message. A save that no longer loads is still written: the file is the reader's and git is the undo. The step engine is the page's own fifty lines rather than the docs player's, because player.js is one init function with its panels inside and nothing separates cleanly.

Rejected: a code editor library or a form that builds a rule; saving through git; reading from Fabric; a button that deploys; --port, --no-browser and --json; animation timings. Not yet: which lines each rule matches and the uncovered, dead-rule and order findings, which come with allez audit. → removed by D77

D73 — deployment jobs install one locked tool set (amended by D82, D87)

The sample and consumer pipelines previously installed the current ms-fabric-cli on each run. Its fabric-cicd dependency could change without a commit, and SqlPackage and GitVersion used floating versions too. Deployment now reads exact direct and transitive Python versions from requirements-deploy.lock. The shared setup template pins Python, SqlPackage and GitVersion.Tool and prints the installed versions before authentication. The test pipeline installs that same lock before it runs the unit, sample and consumer-pipeline tests. Both pipeline copies use the explicit ubuntu-24.04 image label; Microsoft can still update the image contents, so this does not claim byte-for-byte reproducibility. Rejected: a custom deployment container and an update bot. The lock and CI tests make a reviewed manual update sufficient, with Build validation required in Azure Repos. → Operating

D72 — the sample's workspace identity grant is withdrawn before release

Amends D65. D65 shipped a solution hook, grant_workspace_identity, that gave a branch workspace's identity Viewer on dev in the binding deploy, so a connection whose credential is the workspace identity could read dev's warehouse from the branch. It ran green as sp-fabric-deploy-nonprod on 21 Oct 2026. It was withdrawn before 0.7.0 was tagged because the requirement is not settled: which source a branch should read (dev's data, or its own), what happens to the grant when teardown deletes the identity, and whether a role on a whole workspace is the right grant at all. Nothing in the sample uses such a connection yet, so the hook proved a mechanism without a consumer. Removed: the hook, its identity.hooks.yml and the allez-data node's pre_deploy_hooks, the Connections page, the hook's page, and Member on dev for the non-prod service principal, which goes back to Contributor. D65's finding stands: a workspace identity connection authenticates as whichever workspace runs the item. allez still creates no connection, and the id is still a per-env literal in parameter.yml (D31). branch-workspace.identity: true stays, so each branch workspace still gets an identity. The open question and the 20 Oct findings are in plan.md.

D71 — the plan keeps colour for the diff, and says when a change only moved ids

The first real plan-test changes section (PR 199) was hard to read in the Azure Pipelines log viewer. The viewer colours every ##[section] line green, the colour of + <item> new, so the one line that mattered looked like six that did not. A phase line is now a bold line on every surface, with no logging command. The dag tree's nodes in the run are bold cyan, the same as the fold titles they open.

A ~ line could not tell an edit from id churn. A binding deploy commits back the ids its branch workspace gave each item, so a merge often changes files where only GUIDs moved. Now, when every changed file of an item exists at both ends and is equal with every GUID masked and line endings ignored, the line reads ~ <item> changed, ids only: <files>, dimmed. The label says what happened, not that it is safe: a notebook repointed to another lakehouse is also ids only. Standard library only, one git show per changed file. Rejected: masking only .platform logicalIds, which misses the ids a binding deploy rewrites inside definitions; a flag to hide the lines. Deferred to allez audit: listing each item's parameter.yml rules under it, and ids no rule covers. → Reading the plan

D70 — the daily workflow page carries a step-through player; the mermaid diagrams stay

The workflow page opens with a player. Its story lives in docs/assets/workflow/story.js; player.js renders each step from scratch. The repo and Fabric workspaces stay visible. Two positions per environment keep the workspace columns aligned during creation and deletion. Items stack inside each workspace. Dotted grey arrows show autobinding (Auto), solid amber arrows show dynamic replacement from parameter.yml ($workspace / $items), and dashed purple arrows show $allez tokens. Lines have no inline labels; the legend names the types and item and arrow tooltips retain complete references. Existing workspace references are visible from the start. The desktop workspace row is capped at 20.5rem and the whole player at 41.5rem, so tall screens leave blank page below instead of stretching Git history and the terminal. Terminal text is slightly smaller than before.

Three connected header buttons fill as each chapter progresses. Five icon-only controls have accessible names and tooltips: play all, previous step, replay this step, play/pause/resume this step, and play next step. Previous shows the completed state. Play all runs from the current step to the end and stops at each approval; Approve continues it. The other controls stop at the current step's end. A subtle Next label beside the current title previews the next step, or says Final step at the end. No control skips an approval. The explanation appears above the panels; the complete command sits above terminal output. The active terminal source is labelled in its panel heading and highlighted. Terminal output clears on transitions between local and Azure DevOps in either direction; Azure DevOps actions and pipeline jobs share one source. Data and Reporting have centred column labels; environments have horizontal labels. Workspace cards use short labels with full names in tooltips and accessible labels, with a gap above the item list. The current deployment phase appears in the Fabric heading rather than taking space inside each card. Other environments are subdued and marked unchanged. Arrows describe item references, not data movement.

On a laptop, Local files, Azure DevOps and Fabric share one row, with Git history and the terminal below. File contents are not shown; the extra panel left too little room for the workflow. The tree highlights the file used by the current step without making filenames clickable. The player fits the viewport with scrollable source and output panels, rather than reducing text size to fit every line. Narrow screens stack panels. Local files always lists config.yml and parameter.yml. Added files use green and A until committed; modified files use amber and M. The terminal takes 70% of its row. Branch names are tags after the commit message, on the right. Main uses pink, the feature branch teal, and release tags gold. A deleted remote branch does not remove the tag for the local feature checkout. Orange identifies local files and Git, blue identifies Azure DevOps, and green identifies Fabric. Panel backgrounds mix 90% theme background with their identifying hue; headers use 80%.

Bracketed IDs are illustrative. Azure DevOps shows both pipelines, expanding the active one's stages and jobs. Running jobs have a filled background, status word and pulsing indicator. Commands appear immediately. Step introductions allow 2.2 to 6.5 seconds to read the explanation. File-context changes hold for 4.2 seconds; object changes for 3.2 seconds; notes for 3.6 to 7.5 seconds according to length. Play all takes about two minutes. Steps unique to allez play slowly: provision, the first binding deploy and its Git commit, $allez resolution, apply, the merged-workspace cleanup and the Prod-only refresh hook. Repeats play quickly: Test, Prod, the second dag node, the second provisioned workspace and plain Git steps. New terminal lines fade in place and changed Fabric items get a brief outline. Motion occurs during playback only, and reduced-motion settings disable it. Approve stops playback at both gates beside a plan summary and states that approval only continues the demo. Timings do not represent real deployment durations.

The repo shows local and remote branch state separately. Labels show pushes, Fabric's binding commits and the local pull. A PR completed in Azure DevOps does not switch the local checkout. The story starts on main with a Warehouse, semantic model and report, then adds a notebook and follows it through dev, test and prod. It omits allez-orchestration from the animation and its displayed YAML. The mermaid diagrams stay in the tabs for the Azure DevOps wiki and the site's no-JavaScript fallback. File excerpts and output are maintained by hand, so nothing enforces agreement with the sample. Rejected: another growing sequence diagram beside the existing panes, a framework or bundler, and a speed slider. → Daily workflow

A page mounts a player with <div data-allez-story="<name>">; each story file registers itself in window.ALLEZ_STORIES. A second, Fabric-only story for the why page, showing what branch-out leaves pointing at dev, was built and then taken out. The page's text already says it, and the story needed its own layout, legend and terminal source in the engine. The workflow story is the only one.

D69 — the protected branches are the root protected-branches:, not a list in the code

Amends D35 and the "dropping release/*" rejection in D63. The refusal that stops a branch workspace being made for a deployed branch (D35, D51) carried its list in workspace.py: main, master, release/*. That list is Release Flow's, which D63 replaced, and the daily workflow page says a repo may run Git Flow instead. Such a repo deploys from dev and test, which nothing refused, so queuing provision-feature.yml on dev would make [repo]-[dev]-[…], git-connect it to dev and commit rebound ids to a deployed branch, the accident D35 exists to stop. The strategy is the pipelines' to encode and the pipeline files are the consumer's, so the CLI's last line of defence now reads the same file as the rest of the configuration: protected-branches: in the root allez.yml, beside environments:, a list of branch names or * globs, matched on the branch's slug so a pattern and a workspace name are checked the same way. Absent, the list is the old one, so no existing repo changes behaviour; [] is legal and protects nothing. apply, provision and deploy --branch-workspace refuse a match before any fab call naming the branch, the matched entry and the key; teardown keeps a workspace whose slug matches and reports it as protected. The pipeline's own first step, an allow-list for feature/*, is unchanged: it is the consumer's file and the first line, edited with the rest of the pipeline. Rejected: dropping release/* from the default (a changed default costs every existing repo something; the refusal still costs nothing); deriving the list from the pipeline YAML (allez reads no pipeline, and the deploy pipeline names environments, not branches); an allow-list key (the pipeline guard already is one, and the CLI's job is naming the branches that must never be bound); a sub-folder override (which branches are deployed is the repo's, not one workspace's). → Branch workspaces · allez.yml

D68, D62, D34 — a warehouse's schema is SqlPackage's, from a hook; a SQL database's is Fabric's definition API (D34 superseded by D97; D68 amended by D97, D99)

D34 is superseded by D97: a warehouse's schema now goes through the same hook as a SQL database's, publish_sqlproj since D99. D68 is amended by D97 to take a warehouse.

D34 (warehouse). fab deploy creates a Warehouse from .platform and nothing more. Fabric's Warehouse REST API has no definition operations (getDefinition answers OperationNotSupportedForItem), so fabric-cicd sends none, and the dbo/**/*.sql files reach a workspace only through git sync, which test and prod never use. The sample's publish_sql_package hook is Microsoft's stated CI/CD path: dotnet build of the item's .sqlproj in a temp copy, then sqlpackage /Action:Publish to the SQL endpoint found through fab api. A change that would lose data stops the deploy. It is a hook, not a pipeline step, because the schema must land after the warehouse publish and before the ingest notebook, and only the dag orders that. Identity is Authentication=Active Directory Default, the Azure CLI login of the pipeline task or the developer, so the deploy step runs inside the AzureCLI task and allez holds no secret (D28). Costs: the .NET SDK and SqlPackage on agents and dev machines, and one shipped hook that shells past fab, kept to one run_command seam. Rejected: a SqlPackage pipeline step after allez deploy (the model refresh in the dag fails first); git-connecting test and prod (git sync cannot parameterize, D20); a Lakehouse in the sample instead (dodges the question a team asks first).

D62 (SQL database, first shape). A Fabric SQL database holds what a warehouse is wrong for: a control table the pipelines read. It has its own sub-folder and workspace, allez-orchestration, first in the dag with allez-data depending on it, which also gives the sample a three-node chain. fabric-cicd deploys the type as a shell, so the same SqlPackage hook published it at first. Its server is properties.serverFqdn and its database properties.databaseName, not the item name. Its target platform is SqlDbFabricDatabaseSchemaProvider with the Microsoft.SqlServer.Dacpacs.DbFabric package; SqlPackage refuses an Azure SQL v12 project. Seed rows are a MERGE in a post-deployment script, so every deploy runs it and a second deploy changes nothing. An orphaned SQL database is deleted on unpublish only with features: [enable_sqldatabase_unpublish]; the sample leaves it unset, as it does the warehouse's flag. Fabric rewrites the .sqlproj on every commit from a workspace (D46), which is why the script first lived under .deploy/sql/ with a post_deploy: step key. Rejected: the database inside allez-data; seed rows as INSERTs in the table file (a project declares objects, DacFx does not apply rows); a seeding notebook; TRUNCATE then INSERT (wipes a row an operator edited).

D68 (SQL database, today). The .deploy/sql/ shape hid the script from the Fabric editor and did nothing for saved queries. A consuming repo on 0.6.0 lost its pre- and post-deployment scripts on 19 Oct 2026, because a branch's commit-back deleted the .sharedqueries/ folder and project references that the branch database never had. Fabric's own design is that saved queries, and the scripts marked among them, are item state. On commit it writes them to .sharedqueries/ and references the marked ones from the project (<PreDeploy Include>, <PostDeploy Include>). Its definition API in sqlproj format takes the project and every .sql file, builds, publishes the schema, deploys the queries and runs the scripts. Proven the same day: fab import cannot push the type (it sends no format and Fabric answers MissingDacpacFile), but fab api …/updateDefinition with format: sqlproj succeeds in about 40 seconds. So the library hook publish_sql_database sends the item folder unchanged through two Fab methods, update_item_definition and wait_for_operation. The sample's seed sits in .sharedqueries/ with the project referencing it as Fabric writes it. publish_sql_package loses post_deploy and keeps the SQL database only as the schema-only opt-out. A hook never writes marks into the project; the next commit from a workspace would rewrite them away. The first cold run (run 647) proved the loop: the fresh database held both shared queries and the reference, and commit-back wrote them to the branch unchanged. A push that dropped a populated column failed with SqlMSBuildError DataLoss, the same guard SqlPackage applies, so forward-only schema changes stay the rule for both engines. Rejected: the SQL database path inside publish_sql_package (that file shells past fab and is copied into every consumer; a fab-only hook ships in the library and is tested against the fake); SqlPackage for the schema plus the API for the queries (the API overrides the whole definition); restoring .sharedqueries/ after Fabric's commit (fighting Fabric on every portal commit). Still to record: whether allez apply is routed through the same two methods. → publish_sql_database · publish_sql_package · What a deploy does not carry · Sample

D67 — refresh_semantic_model takes Power BI's enhanced refresh body and reports what the refresh did (amended by D99)

The hook sent a plain POST …/refreshes and polled the history until the newest entry left Unknown: one line at the start, one at the end, and on failure serviceExceptionJson alone. For the one step that moves data into a model, the deploy log said the least. Power BI's enhanced refresh is the same endpoint with a body (type, commitMode, maxParallelism, retryCount, objects as tables and partitions, and more) and, for that request only, an execution-details endpoint with per-object status, per-attempt metrics, engine messages and each attempt's error, plus a cancel. So the step takes one optional key, request: the body in Power BI's own keys, passed through unchanged with notifyOption: NoNotification added, validated only as a mapping (a bad key is Power BI's 400, surfaced whole). With it the hook reads the refresh's id from the POST's Location / x-ms-request-id headers, which fab api prints only with --show_headers, so Fab.api(with_headers=True) returns the body and the headers as a pair (proven 20 Oct 2026). It polls …/refreshes/<id>, so a scheduled refresh that starts in between is never mistaken for this one, prints each table or partition the poll its status changes with the elapsed time labelled (±<poll>s), and ends with a report: outcome, wall time, each attempt's duration, external query time, CPU, rows and peak memory where present, engine messages, and the objects completed; on failure each attempt's error code and description and the objects not completed, the first error naming the step's failure. On timeout it sends DELETE …/refreshes/<id> before failing, so a step that gave up does not leave the model refreshing under the next one. A plain refresh (no request) behaves as before; Power BI serves no details and no cancel for it, and the timeout message says so. The load check gained one rule, for every hook: a key run annotates as a mapping must be one, since a placeholder only ever yields a string. Proven live the same day on the sample's Direct Lake model: seven partitions each named as its table, executionMetrics empty, so the attempt's duration comes from its start and end times. Rejected: one step key per API field (eight keys to document, validate and keep in step with an API Microsoft extends; a mapping passed through is one key and no drift); the XMLA route pbi-tools takes (its per-partition row counts and durations come from an Analysis Services trace through the tabular client libraries, a second channel past fab — the one exception stays SqlPackage, D34); wildcards in objects (expanding them needs the model's table list, which the REST API gives only for push datasets); a CSV summary (the deploy log is the artifact); a verbose key (the report is a few lines, printed every time); retrying inside the hook (retryCount is the API's own retry, with its own timeout rules). → refresh_semantic_model

D66 — --since lists each node's item changes from git; the base is a deployed/<env> tag every green deploy moves (amended by D92)

The plan an approver reads showed the dag, each node's config, its hooks and its tokens, but not which items the deploy would create, change or delete. Reading the commit range for that was the approver's job. Everything needed is in git: the item roster is the <Name>.<Type>/ folders (D9), and a named environment holds whatever commit last deployed to it. So allez deploy --since <ref> diffs that commit against the working tree per node, scoped to the node's effective item_types_in_scope, and prints one line per item: + new, ~ changed with the files named, - removed with the verdict fabric-cicd will reach from unpublish.skip.<env> and unpublish.exclude_regex, = unchanged, and deploy config changed: for files outside item folders. Rename detection is off, because fabric-cicd matches by name and type, so a rename is a removal and a creation. The markers are text and colour sits on top, so the stripped artifact reads the same (D56). Four git reads, no fab, so the plan jobs stay without credentials (D63). The header states the one assumption, that the environment holds the base, and says when the base is not an ancestor of HEAD. A ref git cannot resolve fails before any fab call. The base is a lightweight tag, deployed/<env>, which the deploy step moves after a green deploy to a named environment and pushes; both step templates pass it when it exists and say so when it does not. A failed deploy leaves the tag where it was. The binding deploy moves nothing (recordDeployment: false). Costs: a --since flag, since the base is pipeline state no repo file can hold; a template parameter; three moving tags; persistCredentials on every deploy checkout, and the Contribute and Create tag grant in every repo that runs the pipeline. Rejected: Azure DevOps' deployment records as the base (three REST calls with the job's token, an open question about a Reader grant, and a lookup only Azure Pipelines can make, where a tag is read by git rev-parse anywhere; decided 19 Oct 2026); comparing deployed definitions with the repo (slow, normalised by Fabric, and it needs prod's credential before prod's gate); listing the target workspace (the same credential, and it only detects drift the deploy erases anyway); a file-level diff of each definition (the pull request's job, and base64 parts are unreadable). plan.md had rejected a moving tag for prod's version; a deployed/<env> tag says where an environment is, not what version it holds, and the version tags stay. → Reading the plan · Operating

D65 — allez creates no connection; a connection's credential is the workspace identity, and every workspace that uses it reads as itself (amended by D72)

Amends D31, which deferred $allez.$connections because a connection id is tenant-stable. A branch workspace had no credential of its own: its items used dev's connection, which holds a service principal's or a person's credential, so a branch could never be given narrower access than dev and whoever ran a branch pipeline needed a role on a connection they never made. The draft fix had provision create a connection per branch workspace with the workspace identity as its credential, teardown delete it, a connections: key in allez.yml and a $allez.<sub-folder>.$connections.<name>.$id token. The proof on 20 Oct 2026 removed the premise. A connection created with credentialType: WorkspaceIdentity (fab api connections -X post, 201 as a user, no grant anywhere, no workspace named in the body) authenticates as the identity of whichever workspace runs the item: a Lookup over dev's warehouse from one branch workspace ran as that workspace's identity, the same connection from a second branch workspace ran as the second one's, and each failed with a login error until its own identity held Viewer on dev. So one connection per source, made once outside allez like the git connection (D28) and shared like any other, gives every branch workspace a credential of its own, the id is the same for every workspace, and D31's literal stands. skipTestConnection is refused for SQL; the create passes without it. Thirty-seven connection types take the credential. What is new per branch is only the grant: the identity provision just made has no role anywhere. That is the sample's grant_workspace_identity hook, a solution hook in .allez/hooks/ run as the allez-data node's pre_deploy_hooks scoped to feature, one POST workspaces/<dev>/roleAssignments with principal.type: ServicePrincipal, skipped when the assignment exists; the non-prod service principal is Member on dev for it, because a Contributor cannot assign roles. An Azure source is an RBAC grant outside fab. Rejected: everything in the draft; a credential allez supplies (D28); the grant as a branch-workspace.identity.grants key or a library hook, until a second repo needs it (D52); swapping a connection's type per environment. The hook, the Connections page and Member on dev were withdrawn before release by D72; the finding stands.

D64 — $sqlendpoint and $sqlendpointid on the item token, with fabric-cicd's types and values

Amends D23 and D54. Some references take a SQL endpoint, not an item id: a Direct Lake-on-SQL model's Sql.Database(<host>, …) in expressions.tmdl, a dbt profile's endPoint, any TDS connection string. fabric-cicd resolves $items.<Type>.<Name>.$sqlendpoint and $sqlendpointid for the target workspace, and for another workspace only when it is named literally, which cannot follow --env or a branch (D23). allez's item token stopped at $id, so a model in one workspace reading another's warehouse over SQL had only a per-env literal host, and a branch had no literal to write. So the item token takes the same two attributes, for the same item types fabric-cicd supports ($sqlendpoint: Lakehouse, MirroredDatabase, Warehouse, SQLDatabase; $sqlendpointid: Lakehouse, MirroredDatabase) and with the same values, read from the item's typed GET through one Fab method, get_item_properties: sqlEndpointProperties.connectionString and .id for a lakehouse or mirrored database, connectionString for a warehouse, serverFqdn for a SQL database. The SQL database value was checked against fabric-cicd 1.3.0's own table and a live item on 20 Oct 2026: serverFqdn is the host with its port, host,1433, and fabric-cicd substitutes exactly that. A rule can therefore move between $items.… and $allez.….$items.… without changing shape. The attribute is checked against the type at load, before any fab call, because the pairing is fixed by the item types' APIs and known in advance; a plan checks it too. An endpoint still provisioning (provisioningStatus: InProgress, a lakehouse or mirrored database) fails the node naming the token and the item and says to run again. Rejected: polling until the endpoint is ready (the node's publish already waited for the item; the endpoint usually follows within a minute, a clear "run again" beats a hidden timeout, and the pipeline is safe to re-queue); asking Fabric and reporting a missing field for a wrong pair (a load-time error with the fix is the rule); $queryserviceuri and $ingestionserviceuri for an Eventhouse (nothing in the sample or a consuming repo has one; the dispatch table makes it one line when one does); a per-env literal host, the workaround until now (the host changes when the item is recreated and nobody notices until a refresh fails); fabric-cicd's $workspace.<name>.$items.….$sqlendpoint under each env key (the name is a second copy of core.workspace.<env> and feature: has nothing to put there). The sample's model reads OneLake by id, so the sample does not exercise the attributes; unit tests do, as for $name (D54). → $allez.* variables

D63, D25, D13 — trunk promotion in one pipeline, gated at test and prod; the version is GitVersion's and prod deploys are tagged (amended by D69, D90)

Amended by D90: the flow stands, in allez-example. The version and the v<version> tag are the example's; this repo's own runs of the sample, on GitHub Actions, compute no version and tag nothing.

D13 and D25 (Release Flow, superseded). main deployed dev. A release/* branch cut from main deployed test, then prod behind an approval, and hotfixes were cherry-picked into it. Two pipelines, deploy-main.yml and deploy-release.yml, split because one path filter could leave a release branch undeployed. One point from D25 stands: a plan job is the last job of the stage before its gate, because Azure DevOps evaluates an environment's checks at stage start.

D63 (today). Release Flow could not answer "which version is in which environment": a release was a branch named by date, a run was 20261014.2, nothing was tagged. It also cost a branch per release, a cherry-pick per hotfix, and two pipelines with duplicated stages. Now one deploy.yml promotes main. Dev deploys, sweeps orphans and publishes plan-test; the fabric-test approval deploys test and publishes plan-prod; the fabric-prod approval deploys prod and tags the commit v<MajorMinorPatch>, skipping a tag that exists. The commit is the artifact at every stage and a hotfix is a merge that walks the same gates. The version is GitVersion's, from GitVersion.yml at the repo root (GitHubFlow, main increments minor, ci label until tagged, +semver: in a merge message bumps further), installed as a dotnet tool inside fab-setup-steps.yml so a consumer organisation installs nothing. Every checkout: self fetches full history and tags. This repo's own tags are allez's releases, so the sample's Prod stage deploys without tagging and the v prefix is strict. Rollback is a revert on main through the gates, or Redeploy of an earlier deployment from the Environments page. An approval queue on trunk trains rubber-stamping; batch: true and a timeout on the fabric-test check limit the queue, and the test gate is the user's choice, made with that cost named. The two plan jobs share fab-plan-steps.yml. Unproven until the first merge: GitVersion's exact output on an agent, and whether Redeploy re-runs one stage or the whole run. Rejected: the GitTools Marketplace tasks (an extension per consumer organisation); a step that writes GitVersion.yml when missing; a release: config block; rolling back by running the pipeline on a tag (redeploys dev with old code); test automatic with only prod gated; generated release notes and a version stamped into each workspace (both deferred in plan.md); dropping release/* from the CLI's protected names (D35; the refusal costs nothing). → Daily workflow · Operating · Tenant setup

D61, D56, D42 — on an Azure Pipelines agent, allez speaks the agent's logging commands in a wide, coloured, sectioned console

D42. The pipelines are the main reader of allez's output, and a warn-and-continue (a failed git bind, a refused relation, a hook with on_error: continue) printed as one yellow line left the run green with nothing in its summary. When TF_BUILD is set, output.py writes a warning as ##vso[task.logissue type=warning], an error as type=error, and wraps each workspace (apply, provision) and dag node (deploy) in ##[group]. Off an agent, output is unchanged. Detection, not a flag: the agent already says where it runs. A group closes on an exception so the error after it is never folded away. Rejected: task.complete result=SucceededWithIssues (a partially-succeeded test stage can block prod's succeeded() condition); task.uploadsummary and the like (ADO surface in a CLI another repo consumes by tag).

D56. Off a terminal, rich assumes 80 columns and no colour, so the plan-prod artifact wrapped mid-path and was one grey. output.make_console() is now the one place a console is built: on TF_BUILD it is force_terminal=True, color_system="standard", width=300, highlight=False; elsewhere a plain Console(). 300 because the ADO log viewer never wraps and the longest plan line is about 170 columns. highlight=False because rich's own highlighter would colour every id and path. Logging commands stay raw, so no escape code lands in a ##vso line. The plan step tees the output into the artifact and one sed strips the codes from the file. Rejected: COLUMNS and FORCE_COLOR in every consumer pipeline; --width / --color flags; colour left in the published file.

D61. A fold's title is bold cyan and numbered as the dag tree numbers it (1 ● data/config.yml). Each phase prints a section line (bold; ##[section] on an agent until D71): config, pre_deploy_hooks, parameters, fab deploy, post_deploy_hooks, git. A command a plan prints goes through command: verb bold, flags dim, values cyan, never split by the console width, so Would run: fab deploy … is the most visible line, not the dimmest. Everything inside a phase sits two spaces in except fab's streamed lines, so a fab error found by search still matches what fab printed. Unproven: whether the log viewer renders colour inside a ##[group] title; if not, the title moves to the fold's first line. Rejected: sibling folds per phase (Azure Pipelines does not nest groups); rich panels, tables or spinners; a theme or colour map; a flag for any of it. → allez deploy · logging commands

D60, D58 — guards on main: a CI pipeline, allez-ci, and a pull request template

D60. Nothing ran the test suite unless a person did. .azure-pipelines/allez/ci.yml is one job on a hosted Ubuntu agent with Python 3.12: pip install -e ".[dev]", ruff check src tests, then pytest -m "not sample" and pytest -m sample published as two test runs, the second run even when the first fails, so one build says whether the tool or the example broke. It needs no Fabric access, because the suite fakes the fab boundary. It triggers on a push to main; there is no pr: block, because Azure Repos Git ignores one and a pull request runs a pipeline only through a branch policy, not set for now. Rejected: a Python version matrix; a test of ci.yml's shape; path filters; a type-checker step.

D58. The reviewer had no prompt for the one check nothing else makes: an environment-specific id no parameter.yml rule rewrites, which deploys green and fails at run time (allez audit is deferred). .azuredevops/pull_request_template.md asks what changed and for two boxes: parameter.yml covers every new id, and --plan was read. Short on purpose. Azure Repos reads a template only from .azuredevops/, .vsts/, docs/ or the root of the default branch, so the file lives there and a solution repo copies the folder as is. Rejected: nesting it inside the consumer folder (lands in the wrong place after the copy); a pr: trigger; a template beside the consumer pipelines, which Azure Repos never reads; a longer checklist. → README · Use from another repo

Amended 25 Oct 2026. The template is removed. It was there because nothing checked parameter.yml coverage. allez audit (D75) now does, and validate.yml runs it on every push, so the prompt asked the reviewer for something a pipeline already says.

D59 — deploy --plan prints each node's effective config, the overlay merged as fabric-cicd merges it (amended by D91)

The per-node block the fabric-prod approver reads was read from the sub-folder's config.yml alone, so an overlay that narrowed core.item_types_in_scope to one type printed three. Now the block prints config.yml with the overlay applied the way fabric-cicd applies --params, each line the overlay set marked (overlay). allez copies fabric-cicd's one override rule (ConfigValidator._merge_overrides, 1.3.0): core, publish and unpublish merge setting by setting, an env-keyed dict sets that env only, anything else replaces; features and constants replace whole. Printing only: the overlay still reaches fab deploy unchanged as --params, so allez never becomes the merge authority. The cost is a copy that cannot be tested against the original; the docstring names the function to re-read on an ms-fabric-cli upgrade. The binding deploy's plan line also prints the whole resolved git block, because a wrong connection name is the likeliest failure there. Rejected: --plan --json (nothing reads it, and its schema would become a contract); a validate or show verb; passing the merged config to fab deploy. → allez deploy

D57, D53, D39 — branch-workspace: is what provision applies to a branch workspace, every key on every re-run, with git: nested inside it

D39. The block was workspace: with git: beside it at top level. Both are read only for branch workspaces, since a named environment's workspace is infrastructure (D33), so a prod: Override Map key under it resolved and reached nothing, and the top-level git: read like the dev ↔ main connection, which is made outside allez. Now the name says who reads it and git sits inside it. git stays the root's alone; a sub-folder patching it is refused. The resolved block is printed by provision --plan, not deploy --plan. Rejected: feature-workspace: (feature is not special to allez; any env config.yml names no workspace for is a branch workspace); a comment on a top-level git:.

D53. tags and domain loaded and printed but applied nothing, because the calls were unverified. Verified October 2026: Apply Workspace Tags (POST workspaces/{id}/applyTags, Preview) and Assign To Domain (POST workspaces/{id}/assignToDomain) are core calls a workspace admin can make, and the identity that created the workspace is one. Both go through fab api. Domain first, since a domain's tags exist for a workspace only once it is in the domain. Tag names resolve through GET tags, open to any caller. A domain's name resolves only through the admin API, so domain is {name, id}, both required wherever it is set. A refused domain or tag is a warning; the deploy needs the workspace, not its labels. More than ten tags is a load error, Fabric's limit. Rejected: fab assign for the domain (needs Fabric administrator); a bare GUID for domain; removing tags no longer listed (Unapply wants Fabric administrator, and a branch workspace dies with its branch); item tags in provision (the workspace is empty when it runs, D37).

D57. capacity was applied at creation only, so {default: F4, hotfix: F64} changed nothing once the workspace existed. A re-run reads the workspace's capacityId, looks the declared capacity up by name through GET capacities, and only when they differ runs fab assign .capacities/<capacity>.Capacity -W <name>.Workspace. State first, because the assign moves the workspace and Fabric refuses a move across regions. A capacity the identity cannot see is a warning. --plan prints the assign as conditional. Rejected: an unconditional assign; fab set -q capacityId, which is not settable. → branch-workspace · allez provision · Domains and tags

D55, D52, D29, D27, D26 — hooks are Python files in .allez/hooks/ or the library; a hooks file is a flat steps: list and the dag node says when

D26. The action set (run_notebook, fab_api and so on) was a fixed vocabulary inside the CLI, so every new need meant a allez change. Now a step's action names a Python file exposing run(ctx, **kwargs). allez passes a small context (env, workspace name and id, the Fab wrapper, the console, the node's folder) and the step's remaining keys as keyword arguments, the shape of daxnoob's pre/post-processing wrapper for fabric-cicd. Load-time validation stays: the hook must exist and import, and the step's keys are checked against run's signature. --plan prints the hook and its resolved keys. Cost: a hook is arbitrary Python the CLI cannot vet. Rejected: built-ins plus hooks as an escape hatch (two mechanisms for one job); per-item-type orchestration as in the blog (the dag node is allez's unit).

D27 and D29. Hooks files moved out of the sub-folder's top level into their own folder (now .deploy/hooks/, D44) and lost their pre_deploy: / post_deploy: lists. A hooks file is one flat steps: list, and the dag node names it under pre_deploy_hooks: or post_deploy_hooks:, so the phase sits where a reader looks for order and one file can be "before" on one node and "after" on another. The place is enforced at load because the ignored-files report watches only that folder.

D52 and D55. Five of the sample's six hooks were generic and every consumer copied them, after which the copies drifted. Now action: <name> resolves to the solution's own .allez/hooks/<name>.py beside fabric/, else the library hook src/allez/library_hooks/<name>.py, versioned with the tag. The solution's file wins, so a bespoke tweak keeps its name and a release adding a library hook never breaks a solution. --plan prints (library) or (.allez/hooks). publish_sql_package stays solution code because it shells past fab. D52 first chose .allez-hooks/; D55 folded it into the one .allez/ folder, hooks/ committed and cache/ ignored, so .gitignore says **/.allez/cache/. One trap: a .gitignore left at **/.allez/ hides the hooks and the deploy fails at load naming .allez/hooks/. Rejected: hooks only in allez (a new need waits for a release); library hooks read from the pipeline checkout (pip install git+…@tag installs the package only, so a laptop would find none); a name clash as a load error; a fallback read of .allez-hooks/ for one release (D50); a git check-ignore in the loader. Deferred: a root key for the solution hooks path. → Hooks · Hooks files · Repo structure

D54, D45, D40, D23, D21 — $allez.* tokens resolve inside allez deploy, per dag node, in fabric-cicd's grammar, across a one-level extend: closure (amended by D64)

D21. The tokens were a backfill verb that ran once before any publish, but a token names an item an earlier dag node publishes, so on a first deploy it could not resolve. Resolution now happens immediately before each node's fab deploy, in dag order, and the resolved parameter.yml is restored after the publish so a checkout never carries one env's ids into the next run. --plan shows every resolution node by node. Rejected: keeping the verb for inspection.

D23. The spelling is fabric-cicd's own behind a namespace that says who resolves it: $ marks keywords, bare segments are names. $allez.<sub-folder>.$workspace.$id, $allez.<sub-folder>.$items.<Type>.<Name>.$id. The $allez. head can never collide with a fabric-cicd variable, and an unrecognised shape is a load-time error. Scope is narrow on purpose: fabric-cicd's $workspace.<name>.$id already reaches another workspace by literal display name, so these tokens exist only for what a typed name cannot express, an answer that moves with --env or a branch workspace whose name is derived. Rejected: bare $<sub-folder>.$workspace.$id, one Microsoft release away from ambiguity.

D40 and D45. A token moved into an extend: template deployed green as literal text. Now resolve_parameters reads the main file's extend: list, resolves every token across every file all-or-nothing, and restores each touched file. A template path must resolve inside the sub-folder. D40 walked the closure depth-first; fabric-cicd does not. It logs a template containing extend: as an error and drops its rules while the deploy goes green, so D45 reads one level only and refuses a template that extends again. Rejected: a "keep tokens in the main file" note (a note is not a guard).

D54. Spark SQL's four-part name for a table in another workspace takes the workspace's name and has no id form, so $allez.<sub-folder>.$workspace.$name answers the name allez already holds: core.workspace.<env> for a named env, the derived name for a branch. A config.yml that maps the env by core.workspace_id only reads the name from Fabric (GET workspaces/<id>) and caches it. Quoting is the reference's job. Rejected: $name_encoded (OneLake's ABFS driver refuses the brackets every branch workspace name has); a lookup to prove a name the file already gives. Open: the sample has no reference by workspace name, so only unit tests cover the form. → $allez.* · Splitting a large parameter.yml

D51, D35, D32 — protected branches are refused before any fab call; provision-feature.yml is queued by hand, once per branch (amended by D69, D90)

Amended by D90: the hand-queued run is still the example's shape on Azure Pipelines. In this repo sample-provision.yml runs on GitHub's create event, once per sample/feature/* branch, which is the trigger D32 found Azure DevOps lacked.

D32. Azure DevOps has no branch-creation trigger, so a feature/* push trigger fired on every push and repeated the binding deploy, a full fab deploy into a workspace the developer is editing. Now trigger: none: push the branch, queue the pipeline on it once. A failed run is retried with the same click, since every step is safe to run again. Rejected: a local runbook step (needs the developer to hold the SPN's rights, loses the log); gating the bind on "provision created something".

D35 and D51. The Run dialog defaults to main; build 522 (21 Sep 2026) created [repo]-[main]-[…] workspaces and committed rebound guids straight to trunk. One function in workspace.py refuses main, master and release/*. provision, deploy --branch-workspace and, since D51, apply call it before any fab call, and the pipeline's first step refuses any branch outside feature/*. Rejected: a default branch on the pipeline (Azure DevOps offers none for manual runs); a --force; a --branch on apply (nothing legitimate applies this checkout to another branch's workspace). → Feature workspaces · allez apply

D50, D48, D19 — one validation regime: environments: is required, the loader is the authority, a JSON Schema is pinned to it, and nothing names a previous shape

D19. Declaring environments: used to switch strict checking on, and omitting it switched validation off, so a typo'd env key became a silent no-op. Now the root file must declare the set and every Override Map key and environments: filter is checked against it, always.

D48. allez.schema.json at the repo root describes both scopes, and a # yaml-language-server: $schema= modeline on each allez.yml gives the editor completion and squiggles. A drifted copy is worse than none, so tests/sample/test_allez_schema.py pins the schema's key sets to the loader's and checks that the loader's shape refusals are refusals here too. The loader stays the authority for what a schema cannot know: env names, paths, depends_on targets. One dev dependency, jsonschema. Rejected: generating the schema from the loader; a allez schema verb.

D50. Seven loader branches existed only to recognise a superseded shape and say where it went. Every consumer is on 0.4.0 or later and none of the shapes is in use, so all of it went. The generic checks already refuse each case with the allowed keys listed, and the code, tests and docs describe what the files are, not what they were. The changelog's release entries remain the record of each break. Rejected: keeping the shims one more release; a troubleshooting row mapping old keys to new. → Editor support · Changelog

D49 — what allez does is written down as specs in openspec/; decisions say why, plan.md says not yet

What allez does today lived nowhere as a checkable statement. Now openspec/specs/<capability>/spec.md states it in OpenSpec's shape: one SHALL per behaviour with WHEN/THEN scenarios over what a run observably does, and the D-number it rests on. A behaviour change goes through openspec/changes/<name>/ (proposal, design, tasks, delta specs), opened with /opsx:propose and archived into the specs with /opsx:archive when it ships; openspec/config.yaml carries this repo's rules for each artifact. A spec says what, a decision says why and what was rejected, plan.md says not yet; a plan.md item becomes a change folder the day someone says yes. Rejected: turning plan.md into change folders (most items are undecided); turning this log into specs (a decision records a choice between alternatives, a spec only the winner). → openspec/

D47, D38 — another repo consumes allez as a pipeline resource pinned to a tag, and copies the consumer pipelines as files (amended by D83, D90)

Amended by D83 and D90: the consumer copies and the repository resource are gone. A solution repo starts from allez-example, which holds the pipelines in consuming shape, and names the PyPI release as allezVersion in variables.yml.

D38. A consuming repo declares this one under resources.repositories with ref: refs/tags/<version>, references the two step templates with @allez, and passes its checkout path as allezSource. One ref: pins the CLI and the templates together; a laptop installs the same tag with pip install git+https://…@<tag>. No feed, no wheel, no release pipeline. The pipelines and hooks are copied, not referenced, because they name the consumer's service connection, environments and flow. Costs: checkout: self needs path: s; a template fix ships only with a tag; a consumer outside the organization cannot reach a repo resource. Rejected: an Azure Artifacts feed first (reopen for an out-of-organization consumer); extends templates carrying the whole flow (the flow is one example each team adapts).

D47. A second repo ran all three pipelines this way in October 2026, after deriving its copies by hand from a table of four edits. Now .azure-pipelines/consumer/ holds the pipelines and variables.yml already in consuming shape; a consumer copies the folder and edits the resource name, the ref, its service connections and environments. This repo's own pipelines are the exception that installs from .. tests/sample/test_consumer_pipelines.py guards the copy: every @allez template must exist, every ref: must pin the current version, and no copy may carry a local template, a pip install . or a path into docs/. Found on the way: persistCredentials belongs on the main deploy pipeline (teardown --orphans runs git ls-remote), not on provision-feature.yml. Rejected: generating the copies at release time; the edit table alone. → Use from another repo

D46, D41, D37, D20, D17, D12 — a branch workspace is published by a deploy, then git-connected and committed; dev is published by the pipeline and git-connected to main; nothing is ever filled from git (amended by D94)

Amended by D94: when the branch holds an item the workspace lacks, the binding commit is selective rather than refused, and the bind sets the caller's git credentials from connection-name when they are not configured.

D12 and D20. Git integration imports definitions unchanged, so it can never parameterize and is never the promotion mechanism. Only allez deploy --env dev publishes dev. Dev's git connection to main exists for lineage: every branch workspace's base relation makes it dev's child in the Fabric UI. So publish.skip.dev and unpublish.skip.dev are both false, and dev shows git drift between merge and deploy. Workspace names derive from the branch, so there is no scratch variant.

D17. A fresh branch workspace's items keep whatever guids are committed, pointing at dev's items instead of their siblings. So after provision the pipeline runs allez deploy --env feature --branch-workspace --commit-to-git, a real fab deploy so parameter.yml fires, followed by a commit of the workspace back to its branch ([skip ci]). Three consequences. Covered find_replace rules are is_regex with a dynamic replace (_ALL_: $workspace.$id), because rebound guids get committed and merged and a literal find_value would go stale after the first merge. feature is a first-class env key, and dag nodes take an environments: filter. unpublish.skip.feature: true, so the binding deploy never deletes a developer's uncommitted items. Accepted cost: guid churn in feature-branch diffs. Rejected: rebind at apply (apply stays parameter-free, D1).

D37. Provision used to create, connect, updateFromGit, and only then bind. Two faults, seen 22 Sep 2026 (builds 533 to 535): the sync validates every committed reference against the tenant, so a stale id on main failed every branch-out with MissingDependency; and every item it imported was republished a minute later. Now provision creates the workspace on the capacity and applies allez.yml, leaving it empty and unconnected. The binding deploy publishes into it, then after the workspace's last dag node connects it with git.connection-name, initializes with PreferWorkspace (Fabric matches published items to the branch's folders by logical id) and commits. The deploy refuses to commit if the branch has an item the workspace lacks, rather than delete it, so every item folder must be in item_types_in_scope. A re-run finds the workspace connected and only commits. Rejected: connecting before the publish; PreferRemote plus selective commit (still imports and validates).

D41. provision posted the workspace-relation call right after creating the workspace, but Fabric answers WorkspaceNotConnectedToGit until the workspace is connected, so every relation since D37 failed. The relation now runs in binding.py after the connect and commit, never fatal, since nothing deploys through it. The base is branch-workspace.git.base-env, validated against environments:; unset means no relation. Rejected: a --base-env flag on either verb (a repo file keeps every run reproducible); always dev (a user's env name hardcoded).

D46. One case pulls: provision re-queued after the branch moved, typically because a later dag node failed and the developer pushed a fix. Fabric refuses a commit with incoming changes, and a SQL database is always changed on both sides because Fabric rewrites its .sqlproj on publish. The bind step reads git/status and, when workspaceHead differs from remoteCommitHash, calls updateFromGit with PreferWorkspace and allowOverrideItems, then commits. The workspace winning is right: the deploy just published the branch. This narrows D37: the pull runs after a deploy and touches only items the incoming commits changed. Accepted edge: an item deleted on the branch before such a re-run stays in the workspace. Rejected: catching the commit's error (CommitFailedDueToIncomingChanges is undocumented). → allez deploy · Feature workspaces · allez.yml git

D44, D22 — a workspace folder's non-item folders live under .deploy/

A workspace sub-folder mixed <Name>.<Type>/ item folders with allez's own, told apart only by the missing .Type. Now they sit under one dot-folder: .deploy/hooks/, .deploy/config-overlay/, .deploy/parameters/ (the last a convention). The dot keeps the folder out of item discovery and out of fabric-cicd's and git integration's sight. config.yml, parameter.yml and allez.yml stay at the top. D22 had already renamed the overlay folder from deploy/ to config-overlay/, because deploy/ inside a folder whose purpose is deploying said nothing. One constant, BATCH_DIR, drives discovery, the legal dag shapes and the error message. Rejected: .allez/ as the name (then taken by the id cache); leaving the folders as siblings and documenting the no-.Type rule. → Repo structure

D43, D33, D30, D3 — named environments are infrastructure; branch workspaces are allez's, named from the branch, and are the only sandbox

D3. One workspace per sub-folder per branch, [<repo>]-[<branch-slug>]-[<sub-folder>], each segment bracket-wrapped so single-hyphen names stay parseable. Sprawl is bounded by deterministic naming plus allez teardown.

D30. Ids are never recorded. core.workspace.<env> is a workspace's whole identity; allez resolves the id from the name on every live run and caches it in .allez/cache/ for --plan. D30 also had provision create the named workspaces and git-connect dev through a git.branch map; D33 took that away. Rejected: a dev ↔ main naming convention.

D33. A workspace that lives for years, on a capacity, with roles, a git connection and an identity, is an infrastructure operation, not a deploy tool's side job, and doing it from the deploy pipeline meant the SPN held creator-Admin on prod. Dev, test and prod are made outside allez under exactly the name core.workspace.<env> gives them, with the pipeline SPN as Contributor; dev is git-connected to main the same way. provision --env <named env> is a load-time error; a missing named workspace fails the deploy naming it. Rejected: named-env provisioning as an optional bootstrap (a code path the shipped pipelines never run).

D43. The ask was one workspace a developer owns across branches. Every part of allez assumes a workspace lives for one branch: its name is derived, apply and the binding deploy resolve only that name, the binding deploy refuses a workspace connected to another branch, and teardown either sweeps a derived name or never sees any other. The branch workspace already is a personal sandbox: one pipeline click, the developer's through roles, swept after the merge. A workspace made by hand in the portal sits outside allez and is safe from it. Rejected: per-developer named environments. The shape that would work, the branch recording its sandbox, is deferred in plan.md. → allez provision · Set up · Philosophy

D36 — a dbt job is a fabric-cicd item; the sample ships one, run by Fabric

Fabric's DataBuildToolJob item carries its whole dbt project in its definition, so git integration commits it and fab deploy publishes it unchanged (verified 21 Sep 2026; the docs' definition shape is wrong). The profile names the target warehouse by workspace id, item id and SQL endpoint, all same-workspace fabric-cicd variables, so three regex rules rebind it per environment with no connection object. Runs are the item's own schedule or the Run button; fab job run cannot address the type. Double ownership accepted: dbt builds its schema in the warehouse and the warehouse's git export carries those objects, so the schema hook's exclude: keeps the dbt-owned schema out of the build copy (DacFx treats dbt's three-part names as external, SQL71561). Rejected: dbt from a hook; a run hook now (deferred until a deploy must prove a build). → fabric-cicd files · What a deploy does not carry

D31 — native over bespoke: schedules ride in .schedules; $allez.$connections is deferred (amended by D65)

Fabric keeps an item's schedules in a git-tracked .schedules file that fab deploy publishes and parameter.yml can rewrite per env, so the set_schedule hook is gone. A semantic model's refresh schedule is Power BI's: one fab_api step. $allez.$connections.<name>.$id is deferred: connection ids are tenant-stable, so a per-env literal does the job. To prove on the tenant: fabric-cicd #560 / #981 report a notebook's schedule not updating when the item exists. Rejected: keeping set_schedule as an example the sample cannot exercise. → Hooks files

D28, D24, D14 — allez carries no secret; the git connection is made once with fab mkdir and named by git.connection-name

D14 made the connection the provisioning SPN's secret in Key Vault, resolved by reference (env var, then .env, then az keyvault), with provision creating the connection. Not a PAT, because a PAT is a person. D24 let git.credential be omitted when git.connection-name named an existing connection, so placeholder guids stopped being committed. D28 removed the rest: secrets.py, git.credential and provision's connection creation are gone. A connection holds a credential and allez holds none. The connection is created once, outside allez, with fab mkdir /.connections/<name>.Connection -P …, granted to the pipeline identity and named by git.connection-name, now required. Pipelines authenticate with workload identity federation, and Key Vault to pipeline variable is Azure DevOps' job. Rejected: keeping $(VAR) alone, a reference form with nothing left to consume it. Still true from D14: branch-out has no API, so nothing is inherited from the base and capacity, roles and settings must all be declared in allez.yml; role grants take Entra object ids because fab has no name lookup. → allez provision · Set up

D18, D16, D15, D5 — the dag orders config files, is required, and is the whole answer; overlays are partial configs

D5. $items.<Type>.<Name>.$id resolves against the target workspace at deploy time, so referenced items must already be published. An overlay is a partial config merged over config.yml and passed as --params, so the canonical config is never duplicated. D15 put a dag over sub-folders or their batches under workspace.deployment-dag. D16 took it one step further: a node is one fab deploy config, a sub-folder's config.yml or one overlay, with depends_on:. A workspace is wherever its config points, which lets one workspace be revisited (data → reporting → data). An overlay may not set core.workspace*. The dag is the whole answer: a config or hooks file it does not name is ignored and reported, so a file can be parked mid-change. D18 made the dag required, removing the name-order fallback, and required hooks files to be named <purpose>.hooks.yml so the ignored-files report can see one. Rejected: a per-sub-folder dag. → The deployment dag

D11, D10, D6, D2 — the root file is allez.yml, composition not state, and config lives at the highest scope it is true for

Same filename at both scopes; scope is positional, with the Override Map and Inclusion Filter patterns used everywhere since (D11). Lower scopes reference or override, never duplicate: capacity, common roles, the git block, the environment set and the dag are root defaults (D10). The layers are one deploy-config surface (D6). The workspace is the state; fabric-cicd re-reads it every deploy, so a repo-level state file would drift and need locking (D2). → Allez files

D9, D4, D7, D8 — Fabric-native git integration replaced the git-shaped CLI

clone / fetch / status / pull / diff / remote and the sync manifest are gone; push became apply; allez.yml became automation config. The reason is hand-over cost: whoever inherits the repo gets Microsoft-supported features, not a bespoke tool. Accepted: repo folders mirror workspaces 1:1, and git is the item roster. D4: knowledge lives in skills, not agents. D7 and D8 were superseded by D9 the same day.

D1 — the per-item inner loop is not bulk deploy

apply imports one item at a time and verifies with a read-back, a structured per-item pass/fail an agent can branch on. parameter.yml plays no part: branch workspaces only, never promotion. → allez apply