docs(spec): correct six points of spec drift, and neutralise the --push blacklist claims - #66
Merged
Conversation
S30 said the generic ConfluenceMacro carrier emits a ```confluence-xml
fence; the writer actually emits a :::confluence-macro {...} fenced div
(src/mdd/markdown/ir/writer/blocks.py, _render_confluence_macro). The
confluence-xml fence is real but belongs to RawBlock(format=
"confluence-storage") (and any other unrecognised RawBlock format),
handled by _render_raw_block.
S09's "Markdown ↔ storage XHTML" section separately described a
pre-IR markdownify/{=confluence}-fence conversion strategy that no
longer exists in the writer (the {=confluence} shape now survives only
in an ai/rewrite.py comment and a bundled skill). Conversion goes
through the document IR (S28/S29/S30); corrected S09 to say so and
added those specs to "Related upstream specs".
Refs #54
Co-Authored-By: lsimons-bot <bot@leosimons.com>
Assisted-by: Claude:claude-sonnet-5
S09 documented spaces: at the top level of confluence.yaml. src/mdd/search/sources.py registers it as section="confluence", collection="spaces" — i.e. nested under confluence:. Corrected the example config and cross-checked against S19, which already had the nested shape right. S09 also implied sync-space reads output_dir from config. It does not: export-page takes --output directly, and sync-space either takes --output or falls back to default_output_for_space()'s origin-URL heuristic (src/mdd/confluence/export.py). Only mdd search consumes output_dir, to discover configured mirror roots. Documented which command reads the key and how sync-space actually picks its output directory. Refs #54 Co-Authored-By: lsimons-bot <bot@leosimons.com> Assisted-by: Claude:claude-sonnet-5
S39's "Out of scope" section listed Confluence .mddignore wiring as deferred follow-up work. It has landed: src/mdd/confluence/sync/ mddignore.py, wired into sync-space with --ignore (action="append") and --prune-ignored (rejected together with --read-only at parse time, per the argparse help text and _check_prune_readonly). Added a "Confluence wiring" section describing how the matcher applies to a page-title hierarchy rather than filesystem paths: synthetic rel-paths mirror export.build_path_map's tree-walk and sanitisation, then get the same is_ignored / prune_dir treatment SharePoint uses. Updated the flag sections, the Related upstream specs list, and the Out of scope bullet accordingly. Lucid wiring remains real follow-up work and is left as-is. Refs #54 Co-Authored-By: lsimons-bot <bot@leosimons.com> Assisted-by: Claude:claude-sonnet-5
_check_body_safety (src/mdd/confluence/update.py) refuses an empty body and refuses a body under 10% of the remote body's length, overridable per-invocation with --allow-empty / --allow-shrink (argparse flags on update-page). Neither S09 nor S14 mentioned either guard, despite it being the main check standing between a bad local file and a wiped Confluence page. Documented both guards in S09 under update-page, since that is where the implementation and the CLI flags live; cross-referenced from S14 step 4f, since sync's content-push reuses the same update_page() call with neither flag set (push.py calls it with only yes=True), so a sync run has no way to override either guard and records a tripped guard as a push failure in the run summary. Also fixed S14 step 4c, which said archive/unarchive "update[s] the export header" — the code only flips frontmatter status; the header is untouched. S27 already carries a note in its own implementation section explaining why (mdd.confluence.header has no archived variant and _apply_one_archive never touches it), so S14 now states the correct behaviour and points at that note instead of duplicating the explanation. Added S27 to S14's "Related upstream specs". Refs #54 Co-Authored-By: lsimons-bot <bot@leosimons.com> Assisted-by: Claude:claude-sonnet-5
S06's introduction said "38 specs, 14 research notes". Actual counts
today: 37 files matching docs/spec/S*.md, 15 matching docs/research/
R*.md (000-*.md index files excluded from both, matching the naming
convention in 000-specs.md). The issue's reported 37/15 was correct;
the spec's 38/14 was not.
Since this is exactly the kind of number that drifts every time a
spec or research note is added or renumbered — as this very report
demonstrates — reworded the sentence to not carry a count at all
("dozens of specs, a similarly-sized pile of research notes") rather
than replace one stale number with another that will be stale again
soon.
Refs #54
Co-Authored-By: lsimons-bot <bot@leosimons.com>
Assisted-by: Claude:claude-sonnet-5
… S07 rewrite S07 §2.4/§2.6 are being rewritten (parallel PR) to describe the confidentiality blacklist as entry-point gating rather than --push-only gating: a blacklisted Confluence space or SharePoint site aborts the whole command, not just the push step. Several lines in S09, S14, and S39 asserted the old --push-only trigger explicitly enough to contradict the rewritten S07 once both land. Neutralised each one to describe the blacklist gate's role without asserting when it fires, deferring to S07 as the single source of truth for the trigger condition: - S09 "Confidentiality" paragraph no longer says "--push (...) consults the blacklist" / "local-only export is unrestricted". - S14's two mirror-backend-push bullets and its Related-upstream-specs entry no longer tie the blacklist gate to the push step specifically. - S39's five blacklist mentions (intro, "Precedence vs. utils/ blacklist.py", the "runs before the blacklist" design note, Related- upstream-specs, and the Out-of-scope bullet) no longer describe the blacklist as a "push-side"/"destination-push" gate contrasted with .mddignore's "pull-side"/"source-pull" filtering. Reframed the real, trigger-independent distinction instead: the blacklist is a coarse whole-space/whole-site gate, .mddignore is a fine-grained per-path filter; the two do not interact regardless of when either fires. Refs #54 Co-Authored-By: lsimons-bot <bot@leosimons.com> Assisted-by: Claude:claude-sonnet-5
S27's own Requirements-level "Tests" bullet said archive "flips frontmatter status and export header" (unarchive flips them back), while its Implementation Notes §"Export header for archive / unarchive" (further down the same file) correctly says the header is never touched — mdd.confluence.header has no archived variant and _apply_one_archive only flips frontmatter status — and frames that as the deliberate minimum-viable choice. The code agrees with the Implementation Notes. Corrected the Tests bullet to match, with a pointer to the existing correct explanation rather than restating it. No change to the Implementation Notes section itself; it was already right. Co-Authored-By: lsimons-bot <bot@leosimons.com> Assisted-by: Claude:claude-sonnet-5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Docs only — nothing under
src/ortests/. Covers items 1, 4, 5, 6 and the metanote of #54; items 2 and 3 (S07) land with the #50 fix. In every case the code is
what ships and the spec was wrong. Every claim was re-verified against the code
before the spec was edited; all of #54's claims in this scope held up.
1. S30 + S09 — macro carrier serialisation
_render_confluence_macroemits a:::confluence-macro {name="…"}fenced div withthe depth-aware colon scheme, not the
```confluence-xmlfence S30 claimed._render_raw_blockis what emits that fence — forRawBlock(format="confluence-storage")and any other format the writer does not recognise, which S30 also did not say.
S09 separately described a pre-IR
markdownify/```{=confluence}strategy.That form now survives only in a comment in
ai/rewrite.pyand a bundled skill;S09 now describes the IR path, and gained the S28/S29/S30 links it was missing.
The reader side was already correct and is untouched.
4. S09 — config schema and
output_dirspaces:is registered assection="confluence", collection="spaces"— nested,not top level. S19 already documented it correctly, so S09 was the outlier rather
than a competing source of truth.
sync-spacedoes not readoutput_dirfrom config at all: it uses--output, ordefault_output_for_space()'s git-origin-URL heuristic.mdd searchis the onlyreader. Now stated explicitly.
5. S39 — Confluence
.mddignorewiring is no longer deferredIt has landed:
confluence/sync/mddignore.pybridges the matcher to the page-titlehierarchy via synthetic rel-paths, wired to
--ignoreand--prune-ignored. S39listed it as follow-up in five places, including a self-contradiction where it said
Confluence supports
--prune-ignoredand then said Confluence support was pending.Lucid's follow-up statements are deliberately left alone.
6. S09 + S14 — the undocumented body-safety guards
_check_body_safetyrefuses an empty body, and a body under 10% of the remotelength, overridable with
--allow-empty/--allow-shrink. These are the mainthing standing between a bad local file and a wiped page and were in no spec.
Documented in S09, where the flags and the implementation live, with a
cross-reference from S14 step 4f rather than duplicating the thresholds in two
places. Tracing the sync path turned up a detail the issue did not have:
sync-space's push callsupdate_pagewith noallow_empty/allow_shrinkkwargs, so sync always applies the strict guard with no override available, and a
trip becomes a recorded push failure in the run summary rather than a crash.
S14 step 4c said archive "update[s] the export header"; the code only flips
frontmatter status. Fixed to match, pointing at S27's implementation note.
Meta — S06's counts
"38 specs, 14 research notes" was wrong on both: it is 37 and 15. Rather than
swap in today's numbers, the count is dropped — this sentence is the thing that
just drifted, it will drift again (this batch is touching specs right now), and the
point of the sentence is "there is a lot of prior art and none of it is operator
docs", not a census.
Beyond #54: two things found on the way
S27 contradicted itself. Its Tests bullet said archive "flips frontmatter status
and export header", while its own implementation note correctly says the header is
never touched. The code agrees with the note. Fixed, in its own commit — S27 is not
one of #54's points. Worth having, because S14 now points at S27 as the authority
for exactly this.
Eight
--push-triggered blacklist claims neutralised. The #50 fix movesConfluence blacklist enforcement to the sync/export entry points, so a
blacklisted space aborts regardless of
--push. A sweep for the claim rather thanthe filename found it in three specs: S14 (three places), S09 (including a restated
copy of S07 §2.6's "local-only export is unrestricted"), and S39 (five places — its
whole precedence argument rested on a pull-vs-push contrast, now reframed onto
coarse-gate-versus-fine-filter, which holds either way).
None of these re-specify the new mechanism; each drops the trigger claim or defers
to S07, so this does not collide with the #50 branch.
Gates
mise run spec-checkexit 0 ·mise run cigreen, 2415 passed, 88.03% coverage.Refs #54
Co-Authored-By: lsimons-bot bot@leosimons.com