Discussion Bridge for Astro Machine Manual
This is the exact, reusable implementation memory for Discussion Bridge for Astro. Use it with the Human Manual to generate a site-specific runbook. It may name variables, paths, endpoints, and scopes; it must never contain real secret values.
1. Product And Package
Section titled “1. Product And Package”product: Discussion Bridge for Astropackage: astro-discussion-bridgepackage_root: packages/astro-discussion-bridgecli_source: packages/astro-discussion-bridge/src/cli.tssync_source: packages/astro-discussion-bridge/src/sync/index.tsimport_source: packages/astro-discussion-bridge/src/import-existing.tspublic_docs: docsdefault_starlight_dir: src/content/docsdefault_astro_dir: src/contentPackage commands run from the consuming Astro project root:
npx astro-discussion-bridge --helpnpx astro-discussion-bridge check-discourse [options]npx astro-discussion-bridge publish-new [docsDir] [options]npx astro-discussion-bridge sync-existing [docsDir] [options]npx astro-discussion-bridge publish-and-sync [docsDir] [options]npx astro-discussion-bridge import-existing [docsDir] --topic URL[,URL] [options]sync is a backward-compatible alias for publish-new; do not use it in new
runbooks because the side effect is less obvious.
2. Site-Specific Generation Inputs
Section titled “2. Site-Specific Generation Inputs”Collect and validate before generating a runbook:
site_name: requiredrepository: requiredproject_root: requiredenvironment: dev | staging | live | placeholderframework_preset: astro | starlightsite_url: required absolute HTTPS URL for live usediscourse_url: required absolute URLpost_as: preferred request actor for live writeslegacy_api_username: optional compatibility fallbackpublishing_key_location: private reference only; never key valuediagnostics_key_location: private reference only; never key valueactive_target: optional logical target namecomments_display: simple | full | fullInteractivedeployment_target: optional, e.g. Cloudflare Pagesverification_urls: required for live laneslanes: - name: required docs_dir: required route_base: optional source_mode: astro-managed | discourse-managed | discourse-imported category_id: required for create/update intent tags: [] listed: true | false managing_page_rule: exactly one managing Astro page per topic recovery_owner: required for productionReject or pause generation when source mode, route base, public URL, destination forum, category, or managing page is unknown.
3. Key Model
Section titled “3. Key Model”Publishing Key
Section titled “Publishing Key”Use for publish-new, sync-existing, and publish-and-sync.
Exact settled granular scopes:
categories:listcategories:showposts:editposts:listsearch:showtags:listtopics:writetopics:updatetopics:readtopics:statusKnown allowed-parameter entries from the Discourse granular-key UI:
categories show: id=anyposts edit: id=anysearch show: q=any, page=anytopics write: topic_id=anytopics update: topic_id=any, category_id=anytopics read: topic_id=any, external_id=anytopics status: topic_id=any, category_id=any, status=any, enabled=anyThe API user also needs the forum/category authority required by the action. Retitling replied topics and changing listing status may require staff or moderator capability even when the key has the matching endpoint scope.
Diagnostics Key
Section titled “Diagnostics Key”Current fallback: global/admin-capable, read-oriented setup key. Use through
check-discourse only; that command performs the applicable site metadata,
capability, and setup reads. Do not use this key for publishing, reconciliation,
or routine runtime/deploy paths.
Target future state: a granular diagnostics/read key after required Discourse site metadata scopes are confirmed.
Required Credential Record Blocks
Section titled “Required Credential Record Blocks”Every key-creation interaction MUST present the applicable block with the key description, user, scope, and permissions. The operator MUST copy the block into the respective protected credential file above the secret value. Never request, echo, log, or write the real key outside the protected credential store.
Publishing key:
Purpose: Runtime publishing granular keyUse: publish-new, sync-existing, publish-and-sync, check-discourse basic limitsBot user role: Admin currently; intended future runtime posture is non-admin or least-privilegeKey scope: GranularOperational rule: Use this to validate the minimum permissions needed for normal bridge publishing.Diagnostics key:
Purpose: Diagnostics/setup keyUse: check-discourse onlyBot user role: AdminKey scope: Global or admin-read capableOperational rule: Do not use in CI/build unless explicitly intendedThe complete credential-file templates, including the settled publishing
scopes and secret placeholder, are in docs/KEY_MANAGEMENT.md.
Environment Variables
Section titled “Environment Variables”DISCOURSE_URLSITE_URLDISCOURSE_POST_ASDISCOURSE_API_USERNAMEDISCOURSE_API_KEYDISCOURSE_DIAGNOSTICS_API_KEYDISCOURSE_CATEGORY_IDDISCOURSE_TAGSDISCUSSION_TARGETDISCUSSION_PAGE_URLDISCOURSE_NOTIFY_RECIPIENTSDISCOURSE_TITLE_MIN_LENGTHDISCOURSE_MAX_TOPIC_TITLE_LENGTHDISCOURSE_MAX_POST_LENGTHDISCOURSE_MAX_TAGS_PER_TOPICDISCOURSE_MAX_TAG_LENGTHResolution behavior:
- CLI values override environment values.
check-discourseprefers--diagnostics-api-keyorDISCOURSE_DIAGNOSTICS_API_KEY, then falls back to the publishing key.--dry-runpublish/sync does not require API username/key.- live publish/sync/import requires
DISCOURSE_API_USERNAMEandDISCOURSE_API_KEY.
request_actor: preferred_inputs: environment: DISCOURSE_POST_AS cli: --post-as publishOnBuild_lane_or_default: [postAs, postAsEnv] legacy_fallbacks: environment: DISCOURSE_API_USERNAME cli: --api-username publishOnBuild_lane_or_default: [apiUsername, apiUsernameEnv] precedence: - explicit_or_lane_postAs - named_postAsEnv - DISCOURSE_POST_AS - legacy_apiUsername_controls request_header: Api-Username live_output: "Post as: USER" diagnostics_output: "Request actor" changes_existing_topic_owner: falseapi_key_authority: user_level: All_Users: may_act_for_supplied_Api_Username Single_User: bound_to_selected_user scope: values: [Global, Read-only, Granular] purpose: endpoint_and_action_authority invariant: user_level_and_scope_are_independentservice_identity: collision_rule: no_actual_or_visually_ambiguous_nonhuman_identity_across_forums naming_rule: role_plus_origin_system candidates: - { username: editorbridgeforum, public_name: Discussion Bridge Forum Editor } - { username: editorcanforum, public_name: CAN Forum Editor } verify_before_create: [normalized_username, site_length_limits, availability] preserved_source_identity: obbba-bot inventory_group: name: special-admin grants_admin_status: false grants_API_rights: false grants_category_permissions: falsediscourse_mermaid: official_existing_option: type: theme_component meta: https://meta.discourse.org/t/discourse-mermaid/218242 repository: https://github.com/discourse/discourse-mermaid-theme-component distinct_paths: - existing_official_theme_component - fork_or_extension_of_theme_component - separate_Discussion_Bridge_for_Discourse_plugin - upstream_Discourse_change forbidden_term: Discussion_Bridge_Mermaid_plugin tier_1_API_only_dependency: none4. Source-Mode Contract
Section titled “4. Source-Mode Contract”| Source mode | Required operational guard | Writable by publish/sync? |
|---|---|---|
astro-managed |
explicit ownership decision | yes |
discourse-managed |
discussionSync: false |
no |
discourse-imported |
discussionSync: false until explicit promotion |
no |
Current implementation facts:
discussionSync: falseis read and enforced by sync preflight.- A guarded page reports
skippedwith reasondiscussionSync is false. - Frontmatter parse/update paths accept LF and CRLF boundaries and preserve the source file’s existing line-ending style when writing updates.
- Sync preflight remains guard-driven, while the reviewed import path generates
discussionSourceMode: discourse-importedand booleandiscussionSync: false. - Import preserves the Discourse topic ID and URL.
Therefore, the required Alpha import procedure is:
- run
import-existing --dry-run; - run the live import only after reviewing the destination;
- verify the generated source mode, boolean sync guard, topic ID, and URL;
- review frontmatter before any directory-wide sync;
- remove the guard only after explicit promotion to Astro ownership.
5. Frontmatter Contract
Section titled “5. Frontmatter Contract”Line-ending invariant:
accepted_frontmatter_boundaries: - LF - CRLFupdate_behavior: preserve existing source line-ending stylerequired_regression_case: discussionSync false is enforced for CRLF frontmatterrequired_write_regression: frontmatter updates preserve CRLF source stylePublishing may write:
discussionTarget: "community" # only when a target is activediscourseTopicId: 123discourseTopicUrl: "https://forum.example.com/t/example/123"discussionSourceHash: "sha256-value"discussionLastSyncedAt: "ISO-8601 timestamp"Import currently writes:
discussionTarget: "community" # optionaldiscourseTopicId: 123discourseTopicUrl: "https://forum.example.com/t/example/123"discussionSourceHash: "sha256-value"discussionImportedAt: "ISO-8601 timestamp"discussionCommentsDisplay: "full" # only when requestedOperator-supplied controls and overrides include:
discussionSync: falsediscussionCommentsDisplay: simple # simple | full | fullInteractivediscussionSummary: | Curated Discourse-safe companion content.discourseCategoryId: 5discussionTags: "product, docs"6. Command Contract And Expected Results
Section titled “6. Command Contract And Expected Results”check-discourse
Section titled “check-discourse”Reconciliation contract:
explicit_existing_topic: input: discourseTopicId embed_config: '{ topicId }' page_url_reconciliation_required: false verification: read and validate the explicit topic directlyurl_owned_topic: input: no discourseTopicId embed_config: '{ discourseEmbedUrl: embedUrl }' page_url_reconciliation_required: trueKeep embed-info/exact-URL-search results in diagnostics even for explicitly
linked topics, but do not treat a 404 or no URL owner as a topic failure when
the explicit topic is healthy.
Read-only endpoints may include:
/site/settings.json/site.json/categories.json/tags.json/embed/info?embed_url=...exact URL search fallbackCanonical command:
npx astro-discussion-bridge check-discourse \ --discourse-url https://forum.example.com \ --category-id 5 \ --tags product,docs \ --page-url https://docs.example.com/example-page/Expected output sections:
Discourse URLSite settingsSite capabilitiesLimitsTag capabilitiesCategoryRequested tags / Tag inventory / Tag issuesSetup issues / Setup warningsReconciliation lookup (when page URL supplied)Exit code is nonzero when tag issues or setup issues exist. Unavailable metadata may be reported as warnings/unknown values.
publish-new
Section titled “publish-new”Creates only missing companion topics and skips linked pages.
npx astro-discussion-bridge publish-new src/content/docs --dry-run --detailsnpx astro-discussion-bridge publish-new src/content/docsExpected per-page statuses include dry-run-create, created, skipped, and
failure output. A live create writes topic link and sync metadata to frontmatter.
sync-existing
Section titled “sync-existing”Updates only pages with discourseTopicId; skips missing links and guarded pages.
npx astro-discussion-bridge sync-existing src/content/blog \ --route-base blog --dry-run --detailsnpx astro-discussion-bridge sync-existing src/content/blog --route-base blogExpected statuses include dry-run-update, updated, unchanged, and
skipped. --force rewrites the managed first post even when the source hash is
unchanged. --unlist changes topic visibility and may require staff authority.
publish-and-sync
Section titled “publish-and-sync”Creates missing topics and syncs linked topics in one explicit run:
npx astro-discussion-bridge publish-and-sync src/content/releases \ --route-base releases --dry-run --detailsUse only after checking that every non-Astro-managed page in the directory has
discussionSync: false.
import-existing
Section titled “import-existing”npx astro-discussion-bridge import-existing src/content/docs \ --topic https://forum.example.com/t/example/123 \ --source-mode discourse-managed \ --site-url https://docs.example.com \ --comments-display full \ --dry-runHero options:
--hero-image PATH|URL--hero-alt TEXTValidation contract:
pairing: bidirectional_requiredhero_alt: non_empty_after_trimexplicit_errors: - bare option - inline-empty value - whitespace-only alt - hero image without alt - hero alt without imageoutput: placement: leading image before raw body image_syntax: angle-wrapped destination alt_escaping: supported internal_path_spaces: supportedPrune option:
--prune-profile community-call-to-actionprofile: community-call-to-actionmode: opt_inremoval_scope: trailing block after horizontal rulemarkers_all_required: - Join the Conversation Today - /signup - Please share how - /c/stories/failure_before_io: - no verified boundary - unknown profile - duplicate profile - bare option - empty valuesuccess_metadata: discussionImportPolicy: pruned:community-call-to-actionAccepted topic references: numeric ID or URL whose host matches
DISCOURSE_URL. Expected statuses: dry-run-import, dry-run-overwrite,
imported, skipped. Existing files are skipped unless --overwrite is used.
--source-mode accepts discourse-imported or discourse-managed, defaults to
imported, and rejects astro-managed. Generated frontmatter always includes the
selected discussionSourceMode and discussionSync: false. Manifest entries
use sourceMode; the same validation is applied before write.
Alpha Import Discovery / Queue Contract
Section titled “Alpha Import Discovery / Queue Contract”selection_modes: curated: input: explicit topics or manifest ordering: preserve caller-supplied order next_in_category: category_discovery: list available categories and subcategories category_selector: ID or unambiguous slug/name category_selection: required before queue preview default_order: created_at ascending tie_breaker: topic ID ascendingfilters_optional: - tags - created_at range - open/closed status - limitordering_optional: - oldest by created_at - newest by created_at - natural topic title/name for numbered collectionsforbidden_order_fields: - bumped_at - last reply - latest activitypre_import: preview_selected_category_queue: required preview_candidates: required exclude_already_imported_topics: requiredalpha_requirement: trueThe stable comparison key for the default queue is (created_at, topic_id).
Community replies must never reorder publishing candidates.
Manifest refresh gate:
command: import-existing --manifest PATH --overwritepurpose: deterministic multi-page refresh with per-topic policyschema: format: strict_json top_level_keys: [version, imports]manifest_entry_fields: - topic - requiredTags - output - commentsDisplay - heroImage - heroAlt - pruneProfilesordering: preserve caller-supplied manifest orderreject: - duplicate_topics - manifest_and_direct_option_mixingvalidation: - runner_revalidation - dry_run_collision_and_path_preflight - path_containment_before_writeswrite_model: - stage_all_entries_before_writes - exclusive_atomic_creation_without_overwrite - atomic_overwrite_with_rollbackregression_coverage: - slug_drift - destination_race - zero_byte_output - path_traversalblanket_update_all_safe: falsepackage_gate: pass_51_of_51obbba_site_live_proof: pass_topics_434_747_751_752_753Every import route has two validated contracts:
WHEREFROM: discourse_base_or_target: required topic_identity_or_curated_manifest_order: required category: required_when_lane_uses_one required_tags_and_filters: validate_against_live_source_before_writeWHERETO: docs_dir: deterministic Astro content root output: safe relative .md or .mdx file public_identity: site_url + route_base navigation: Astro navigation lane invariants: [deterministic, reviewable, path_contained, never_latest_activity]manifest_v1_mapping: wherefrom: [topic, requiredTags] whereto: [docsDir, output, site-url, route-base, astro_navigation_lane_map]future_schema_note: nested from/to may be considered later; do not redesign v1 nowrequiredTags comparisons are case-insensitive assertions against live
Discourse tags and fail before any write. Discourse topic tags may arrive as
strings or objects. output must be a safe relative .md or .mdx path;
nested parent directories are created only after the entire destination passes
validation and containment checks.
Starlight Imported-Page Integration
Section titled “Starlight Imported-Page Integration”Stock Starlight docsSchema() may strip custom bridge fields, and imported
Markdown pages do not contain a hand-written <Discussion> component. A
Starlight consumer must:
- extend
docsSchemawith the Discussion Bridge frontmatter fields; - install
src/components/MarkdownContent.astroat the page boundary; - wire it through
starlight.components.MarkdownContent; - remove per-page explicit
<Discussion>instances when the boundary override is active, preventing duplicate discussions.
Verification requires exactly one discussion instance per linked page with the expected topic ID and display mode.
7. Common CLI Options
Section titled “7. Common CLI Options”--dry-run--details--target NAME--route-base PATH--discourse-url URL--site-url URL--api-username USER--api-key KEY--tags TAG[,TAG]--category-id ID--force--unlist--notify-on-failure--notify-recipients USER[,USER]--title-min-length N--max-topic-title-length N--max-post-length N--max-tags-per-topic N--max-tag-length N--skip-title-validationDo not put real keys on a command line in a runbook; use protected environment variables so keys do not enter shell history.
8. Lanes And Route-Base Rules
Section titled “8. Lanes And Route-Base Rules”URL derivation joins siteUrl, normalized routeBase, and the Markdown path
relative to docsDir, with index collapsed to its parent path.
Examples:
docsDir=src/content/blogfile=src/content/blog/content-lanes.mdrouteBase=blogresult=https://docs.example.com/blog/content-lanes/
docsDir=src/content/releasesfile=src/content/releases/2_1.mdrouteBase=releasesresult=https://docs.example.com/releases/2_1/Pre-write invariants:
- one managing Astro source page per Discourse topic in a run;
- no duplicate managed page URL in a run;
- active
discussionTargetmust match--target/DISCUSSION_TARGET; - display-only comparison pages use
discussionSync: false; - category and tags are checked before live writes;
- computed URLs are reviewed with
--details.
9. Comments Display Contract
Section titled “9. Comments Display Contract”discussionBridge({ provider: "discourse", discourseUrl: "https://forum.example.com", siteUrl: "https://docs.example.com", comments: { display: "full", // simple | full | fullInteractive embedHeight: "800px", className: "discussion-bridge-embed", }, replies: { refreshOnPageLoad: true, refreshEndpoint: "/api/discourse/topics/{topicId}.json", renderMermaid: true, },});simple: Discourse embeddable-comments script; limited display control and metadata.full: Astro-rendered replies; interaction remains in Discourse; browser refresh needs CORS or same-origin proxy.fullInteractive: Discourse full-app iframe; requires full-app embedding and compatible sign-in/cookie configuration.
Rendering boundary:
fullInteractive: owner: cross_origin_Discourse_iframe host_Astro_transforms_or_CSS_cross_boundary: false mermaid_and_table_styling_owner: Discourse ordinary_topic_434_mermaid_theme_result: SVG full_app_embed_topic_434_mermaid_result: raw_code full_app_embed_theme_component_js_loaded: false table_parse: pass table_presentation: weak_until_embedded_CSS immediate_table_path: Discourse common/embedded.scss targeted by embed class mermaid_embed_solution: open_extension_plugin_or_upstreamfull: owner: Astro_rendered_bridge_component parity_review: pass_d7800d7_code_boss_final renderMermaid_default: true renderMermaid_opt_out: false mermaid_version: 11 security_level: strict loading: lazy_when_reply_contains_mermaid failure: preserve_source_and_allow_module_load_retry tables: readable_borders_padding_and_horizontal_overflow repeated_components: claim_each_replies_container_onceembed_class_hooks: comments.className: forwarded_to_window.DiscourseEmbed.className embedClassName_components: [Discussion, DiscourseDiscussion, DiscourseComments]The lazy Mermaid chunk is emitted during a full-capable build and may trigger
Vite’s greater-than-500-kB warning, but browsers fetch it only when a full-mode
reply contains Mermaid.
Verification matrix:
simple: embed loads, full discussion link worksfull: build/render fetch works, browser refresh works, unavailable state worksfullInteractive: logged-out load, logged-in reply/like/quote, mobile height/CSPFor a live signed-in interaction item, record sanitized evidence:
topic_id: expected topicpost_number: created test post numberdirect_post_url: public verification URLcontent_marker: non-sensitive test textpost_count_after: observed countastro_embed_signature_after: topic_id: expected topic full_app: true retired_renderer: absentprivate_account_identifier: omitCreating the reply is an intentional browser-session action, not a package publish/sync/import operation. Verify the forum result read-only afterward.
Fresh-import gate matrix:
cases: - { hero: false, prune: false } - { hero: true, prune: false, alt_text: required } - { hero: false, prune: true } - { hero: true, prune: true, alt_text: required }per_case_assertions: - discussionSourceMode == discourse-imported - discussionSync == false - topic ID and URL preserved - build/deploy/live render pass - comments pass - no writebackDo not use an existing-page migration or signed-in interaction result as a substitute for this matrix.
Component propagation invariant:
Discussion.astro -> DiscourseDiscussion.astro -> DiscourseComments.astroIf topicId is supplied, every layer must preserve it and the browser embed
configuration must use { topicId }. Regression tests must cover this path.
Shipped browser declaration contract:
type DiscourseEmbedConfig = | { topicId: number; discourseEmbedUrl?: never } | { discourseEmbedUrl: string; topicId?: never };
// Both alternatives may also include:// fullApp?: boolean// embedHeight?: stringDo not model topicId and discourseEmbedUrl as simultaneously required or
freely co-present; they represent alternative ownership/linking paths.
Planned boundary-credit contract (not implemented in the current reviewed artifact):
comments_credit: configurable: required candidate_text: - Discussion connection by Discussion Bridge - Discourse connection by Discussion Bridge canonical_product_link: required visually_secondary: required accessible: required supported_modes: [simple, full, fullInteractive] hard_coded_in_site_content: false final_default_and_schema: unresolved10. Cloudflare Pages / Domain Verification
Section titled “10. Cloudflare Pages / Domain Verification”CDN-backed Discourse compatibility evidence
Section titled “CDN-backed Discourse compatibility evidence”verified_at: 2026-07-22forum: https://forum.repealobbba.orgforum_edge: Cloudflare_CDNstatus: PASS_for_exercised_production_workflowsexercised: - check_discourse_and_API_reads - Discourse_topic_imports_to_Astro - target_topic_reconciliation - protected_source_topic_links - fullInteractive_comments - signed_in_reply_behavior - five_live_source_disclosures - source_and_cross_target_no_writeback - canonical_Astro_production_pagesclaim_limit: no_guarantee_for_every_CDN_WAF_cache_configurationpreserve_through_CDN: - Discourse_API_paths - JSON_endpoints - embed_and_full_app_routes - authentication_and_cookies - websocket_behaviordiagnostic_rule: compare_edge_and_direct_origin_then_investigate_cache_WAFDo not record private Cloudflare account labels, rule identifiers, or access values. If edge and origin results differ, capture sanitized path/status/cache evidence and route the CDN/WAF configuration issue to Ops.
Cloudflare-specific values are site inputs, not package constants. Record:
repository: canonical repository URLproduction_branch: mainroot_directory: consuming Astro project pathbuild_command: npm run buildoutput_directory: site-specific Astro output, commonly distcustom_domain: exact public hostnameastro_site: exact public URLdiscussion_bridge_site_url: exact public URLdiscourse_embed_host: exact hostnameaccount_id: explicit deployment account identifier in approved site configworker_name: exact Worker name when using Workersworker_version: deployment evidenceworkers_dev_endpoint: deployment verification endpointWhen more than one Cloudflare account is available, pin the intended
account_id in the approved deployment configuration so CLI/CI selection is
deterministic. Manuals and support bundles should describe the placement without
copying private account labels or login email addresses.
Verification:
- Pages deployment corresponds to the intended commit and canonical source.
- Custom domain resolves and HTTPS is valid.
- Astro
site, Discussion BridgesiteUrl, CLISITE_URL, and the public hostname agree. - Discourse allows that exact embed hostname.
- A lane page and its companion topic resolve in both directions.
- The selected comments mode works on desktop and mobile.
- Cache bypass or narrowly scoped purge is tested before declaring stale output a failed sync/deploy.
- The Worker endpoint and canonical domain serve the intended candidate.
- Generated HTML contains the expected package/topic signature and does not contain the retired renderer signature.
Before deployment commits, inspect the consuming site’s worktree. Preserve and report unrelated pre-existing changes; do not absorb them into deployment-only commits.
Build the exact tracked candidate from a clean checkout or detached worktree before release. A dirty local deletion can hide a stale tracked page or asset reference and make the working-tree build pass when the committed candidate fails. Isolate the correction in its own reviewed commit, rebuild that exact commit, and leave unrelated unstaged changes and untracked artifacts untouched.
11. Known Failures And Recovery
Section titled “11. Known Failures And Recovery”| Failure | Detection | Recovery |
|---|---|---|
| Missing credentials | CLI missing-configuration message | Set protected env vars; rerun dry run. |
Site metadata 403 |
check-discourse unavailable/warning |
Use diagnostics key or explicit preflight limits. |
| Title/body/tag validation | Local preflight failure | Correct content/settings; rerun dry run. |
| Embed URL already taken | Publish error or reconciliation output | Prove ownership, resolve with diagnostics, then link/retry. |
Page URL has no embed owner but topicId is explicit |
Embed-info 404 or exact search has no result |
Validate the explicit topic and generated { topicId }; do not auto-relink. |
| Wrong category/route | --details or post-write verification |
Stop writes; correct lane; assess/relink affected topic explicitly. |
| Active target mismatch | skipped with target reason |
Use the matching --target; do not remove labels casually. |
discussionSync is false |
guarded page skipped | Expected for display/import/Discourse-owned pages; remove only on approved promotion. |
| Topic deleted | topic read failure | Do not auto-recreate; decide restore, relink, or replacement. |
| First post deleted | missing-first-post failure | Restore or choose explicit repair; do not silently replace. |
| Discourse offline | clear network/API failure | Preserve Astro shell; retry after service recovery. |
| Stale CDN output | source/topic correct but public view stale | Verify commit/topic, bypass cache, then purge relevant cache only. |
CLI/build output is authoritative. Failure-notification PMs are best effort and must not be treated as the only failure record.
12. Standard Verification Loop
Section titled “12. Standard Verification Loop”1. Confirm intended package build/version.2. Confirm lane inputs and source modes.3. Run check-discourse.4. Run intended operation with --dry-run --details.5. Review every page URL, target, topic ID, category, tag, and reason.6. Run live command without changing other arguments.7. Verify Discourse topic, first post, metadata, tags, and listing state.8. Verify Astro source/frontmatter and local build.9. Verify deployed URL, comments mode, and cache state.10. Sanitize logs before sharing; update manuals with newly confirmed facts.13. Multi-Target Frontmatter And Execution
Section titled “13. Multi-Target Frontmatter And Execution”The frontmatter values are YAML scalars. CSV fields preserve target order; the binding map is serialized JSON:
discussionSourceMode: discourse-importeddiscussionSync: falsediscussionTarget: repeal-obbbadiscussionSourceTarget: repeal-obbbadiscussionTargets: repeal-obbba,citizen-activistdiscussionPublishTargets: citizen-activistdiscussionPrimaryTarget: repeal-obbbadiscussionTargetBindings: '{"repeal-obbba":{"topicId":434,"topicUrl":"https://forum.repealobbba.org/t/434","status":"synced"},"citizen-activist":{"status":"failed","lastError":"sanitized target error","lastAttemptedAt":"2026-07-22T00:00:00.000Z"}}'Operate one target per CLI invocation:
npx astro-discussion-bridge publish-and-sync src/content/docs --target citizen-activist --dry-runnpx astro-discussion-bridge publish-and-sync src/content/docs --target citizen-activistdiscussionSync: false does not authorize source writeback. The named source
remains protected for discourse-imported and discourse-managed; only a target
listed in discussionPublishTargets is writable. New targeted imports set both
legacy discussionTarget and discussionSourceTarget. A legacy imported page
without the latter protects its legacy discussionTarget.
Run ordered publishOnBuild.lanes sequentially when build-time publishing is
intended. Each lane needs targetName and may override discourseUrl,
apiKey/apiUsername, or named apiKeyEnv/apiUsernameEnv. Do not put secret
values in frontmatter, manuals, or committed configuration.
On failure, inspect only the active target’s binding. Preserve other successful
bindings and retry with the failed --target. A stored error is whitespace-
normalized, truncated, and paired with lastAttemptedAt. A reconciled 422
embed/title collision records the discovered owning topic in the active binding
and clears its failure state. Invalid binding JSON or shape must fail before
network access.
14. Source-Provenance Contract
Section titled “14. Source-Provenance Contract”implementation: commit: a9d2097 review: Code_Boss_PASS tests: 68_of_68_PASS package_dry_run_exports: - dist/source.* - DiscussionSource.astro - package_root_helpers_and_types - astro-discussion-bridge/sourcepublic_api: component: astro-discussion-bridge/DiscussionSource.astro helper: resolveDiscussionSourceNotice types: [DiscussionSourceMode, DiscussionSourceNotice]rendering: placement: near_article_start separate_from: [comments_boundary, Discussion_Bridge_credit] aria_label: Content source prefix: "Source:" default_source_label: Discourse modes: discourse-imported: This page originated in {sourceLabel} and was imported here for publication. discourse-managed: This page is managed in {sourceLabel} and published here for easier reading. astro-managed: no_notice unknown: no_noticesource_url_precedence: - explicit_sourceUrl_prop - discussionImportedFrom - protected_discussionSourceTarget_binding - legacy_discussionTarget_binding_fallback - legacy_discourseTopicUrlaccepted_link_protocols: [http, https]unsafe_or_malformed_candidates: skipno_safe_url: render_notice_without_linkmulti_target_rule: provenance_follows_protected_source_not_publication_targetcustom_props: [sourceLabel, message, linkLabel, class, mode, sourceUrl, targetBindings, frontmatter]site_wiring: plain_Astro: canonical_BlogPost_layout Starlight: MarkdownContent_overrideOBBBA: adoption_status: complete adoption_commit: aa7846d artifact: vendor/astro-discussion-bridge-0.1.0-alpha-a9d2097-f3fbb73e.tgz artifact_sha256: F3FBB73E95D52B5799FBEBE5221298040FD32292EDA8BD76C257C0C19E4267B2 Code_Boss_adoption_review: PASS clean_detached_npm_ci_build: PASS publishOnBuild: false Discourse_writes: none worker_version: 005b9ff2-c880-43e4-b759-31ec2d02bed5 live_routes_verified: 5 per_route: Content_source_asides: 1 source_links: 1 source_forum: forum.repealobbba.org source_label: Repeal OBBBA Forum wording: exact_imported_default_with_custom_label discussion_boundary_preserved: true citizen_activist_topology_gate: separate_and_open15. Alpha Release And Support Inputs
Section titled “15. Alpha Release And Support Inputs”release_scope_doctrine: alpha: nearly_feature_complete_for_declared_product_promise scope_model: cumulative source_of_truth: existing_dashboard_and_build_launch_checklists new_gates_displace_existing_gates: false removal_authority: explicit_Phil_direction beta_primary_work: - usability - compatibility - reliability - performance - packaging - documentation - installation - recovery - support - presentation beta_missing_capability_from_user_evidence: allowed planned_major_pillar_deferral_to_beta: prohibited_by_default long_term_and_layer_3_outside_declared_promise: latertiers: tier_1: transport: Discourse_API plugin_required: false role: free_self_serve_floor optional_discourse_plugin: alpha_vertical_slice_status: accepted_Alpha_goal_pending_implementation_and_live_proof product_repo: separate_from_Astro_package_per_Boss_routing installability: supported_stock_current_Discourse removal_and_rollback_docs: required CAN_normal_topic_baseline: official_Discourse_Mermaid_theme_component v0_1: [full_app_embed_Mermaid, table_parity, explicit_embed_context_detection, automated_tests] ordinary_topic_regression: prohibited live_proof: forum.citizenactivist.network_full_app_embed not_v0_1: [full_control_plane, post_as_user, PM_automation, general_many_to_many] tier_1_dependency: false logical_workspace_path: DiscussionBridge/plugins/discourse-discussion-bridge physical_github_repo: unresolved_Boss_folder_decisionmulti_target_implementation: commit: 60e41e1 review: Code_Boss_PASS tests: 62_of_62_PASS package_check: PASS package_dry_run: PASS frontmatter: discussionTargets: ordered_CSV_target_names discussionPublishTargets: explicit_writable_subset_CSV discussionSourceTarget: explicit_protected_source discussionTargetBindings: JSON_scalar_map_by_target discussionPrimaryTarget: required_when_multiple_linked cli: one_explicit_--target_per_run publish_on_build: ordered_sequential_lanes recovery: retain_success_retry_failed_target_idempotently malformed_binding_state: fail_before_network public_export: astro-discussion-bridge/targetsconnection_job_product_doctrine: public_lead: Every connection has a job explicit_human_purpose: required_product_behavior_not_yet_implemented visible_label_and_CTA_match_purpose: required independent_replies_silently_merged: prohibited primary_vs_additional: explicit later_scope: [relay, promotion, summary] configuration_design_status: pending_review_not_final_not_implemented candidate_fields: [role_or_purpose, audience, callToAction, description, visibility_context, direction_source_ownership, presentation] candidate_roles: [primary-community, public-community, chapter, internal-review, expert-feedback, source-wiki, syndication]bidirectional_CAN_contract: directions: - Astro_managed_page_to_CAN_companion_topic - CAN_topic_to_Discourse_managed_or_imported_Astro_page separate_page_topic_pairs: required explicit_source_ownership: required same_item_writable_both_directions: prohibited loop_prevention: single_writer_per_itemchapter_national_future_design: status: proposed_not_implemented public_frame: Local ownership. National reach. topology: governed_hub_and_spoke_federation supported_directions_to_design: - local_topic_to_mapped_national_category - national_guidance_to_selected_local_categories required_fields: - source_forum_and_topic - source_chapter_identity - parent_child_relationship - mapped_source_and_destination_categories - required_tags_region_chapter_metadata - automatic_or_moderator_approved_promotion - public_private_eligibility - source_attribution_and_return_link - target_specific_title_CTA_description - one_way_first_post_sync_direction - independent_reply_streams - target_specific_failure_retry_idempotency - moderation_ownership_at_both_levels optional_later: post_as_chapter_identity silent_merge_or_ambiguous_writeback: prohibited current_full_forum_to_forum_orchestration_claim: falsealpha_topology_proof: status: bounded_OBBBA_to_Citizen_Activist_live_proof_complete verified_at: 2026-07-22 adoption_commit: 36df91c98a35251edd6ddd657cca42ddf0acdafa adoption_files: - src/components/MarkdownContent.astro - src/content.config.ts - src/content/docs/title i/10101-impact.mdx Code_Boss_review: PASS clean_detached_build: PASS worker_version: 632db326-bc0d-4047-b74b-7e74d3588dbf canonical_page: https://onebigbeautifulbill.us/title-i/10101-impact/ canonical_page_HTTP: 200 production_obbba_target: https://forum.repealobbba.org citizen_activist_target: canonical: https://forum.citizenactivist.network public_description: A community of activists hostname_policy: literal_forum_prefix_identity_in_copy cloudflare_account_ownership: unresolved_Ops_decision bounded_demo_target: https://forum.discussionbridge.dev exact_matrix: - { from: same_selected_onebigbeautifulbill.us_page, to: https://forum.repealobbba.org } - { from: same_selected_onebigbeautifulbill.us_page, to: https://forum.citizenactivist.network } - { from: bounded_demo_credit_pages, to: https://forum.discussionbridge.dev } - { from: multiple_Astro_public_sites, to: https://forum.repealobbba.org } proof_meaning: one_page_multiple_forums: first_two_edges multiple_sites_one_forum: fourth_edge contract: source_target_distinct_from_publication_discussion_targets: required target_list: explicit_ordered binding_fields: [topic_id, topic_url, source_hash, last_synced_at, status, last_error, last_attempted_at] presentation_policy: explicit_primary_plus_accessible_additional_links source_no_writeback: preserve_for_discourse_imported_and_discourse_managed comments_presentation: explicit_primary_plus_additional_linked_or_rendered silent_primary_selection: prohibited write_semantics: recoverable_partial_success successful_bindings_on_other_target_failure: retain retry: target_specific_idempotent_no_duplicate_topics target_specific_surfaces: [diagnostics, dry_run, CLI_output, manuals, live_proof] required_gate: - { target_diagnostics: PASS } - { target_specific_dry_run: PASS } - { publish_and_independent_bindings: PASS } - { unchanged_idempotent_retry: PASS } - { clean_build_deploy_live_binding: PASS } - { protected_source_no_writeback: PASS } exercised_binding: source_target: repeal-obbba source_topic_id: 434 source_mode: discourse-imported discussionSync: false primary_display: fullInteractive primary_fullApp: true publication_target: citizen-activist publication_topic_id: 9 publication_topic_url: https://forum.citizenactivist.network/t/sec-10101-re-evaluation-of-thrifty-food-plan-impact/9 publication_category: { id: 5, name: One Big Beautiful Bill, slug: one-big-beautiful-bill, public: true } publication_tags: [discussionbridge, obbba, impact, title-i] additional_presentation: one_accessible_link publication_interaction: posts_count: 2 highest_post_number: 2 last_poster: discourseadmin Astro_primary_topic_remained: 434 reachability: accessible_Additional_discussions_link evidence_scope: interaction_and_presentation_only source_post_count: 12 source_first_post_updated_at: 2026-07-01T01:35:12Z lane: docsDir: src/content/docs/title i route_base: title-i active_target: citizen-activist broad_root_dry_run: rejected_before_write_due_to_wrong_index_and_route dependency_audit: total: 10 low: 1 moderate: 1 high: 8 resolved: false action: dependency_review_no_automatic_audit_fix known_notices: [Starlight_docs_to_404, chunk_size, punycode_deprecation] temporary_detached_worktree: removed_after_deployment protected_credentials: records_exist: true public_paths_or_values: prohibited legacy_record_format_cleanup: protected_vault_task claim: bounded_same_page_OBBBA_to_Citizen_Activist_topology later_scope: general_many_to_many_administrationrelease_channel: Astro_package: alpha: GitHub_prerelease_plus_npm_prerelease_same_commit semver_example: 0.1.0-alpha.1 exact_first_version: release_gate_decision npm_dist_tag: alpha public_install: npm install astro-discussion-bridge@alpha latest_reserved_for: first_stable_release beta_tag: consciously_chosen_beta_next_or_other_prerelease repo_tarball: development_recovery_fallback Discourse_plugin: npm_distribution_decision_applies: falsealpha_feature_lock: status: locked cumulative_scope_source: docs/BUILD_LAUNCH_CHECKLISTS.md allowed_new_work: - close_existing_promise_or_gate - fix_exercised_behavior - explicitly_approved_scope_change software_tracks: astro_package: free_open_source optional_light_discourse_plugin: free_open_source public_docs_and_community_support: free_open_source paid_value: - implementation_help - handholding - managed_hosting_and_operations - customization - support - consulting third_party_infrastructure: operator_paiddiscussionbridge_dev_dogfood: reviewed_import_support: commit: 1731547 code_boss: PASS tests: 72/72 source_modes: [discourse-imported, discourse-managed] default: discourse-imported rejected: [astro-managed] manifest_field: sourceMode discussionSync: false artifact: astro-discussion-bridge-0.1.0-alpha-1731547-7d8951d1.tgz sha256: 7d8951d15f4b0a4a4f14e238665bc41c28255c6f2cdcb1979105926ba6f4affb node_engine: ">=22.12.0" guide_dry_run: source_forum: https://forum.discussionbridge.dev source_topic_id: 36 destination_route: /guides/how-to-choose-a-discussion-bridge-source-mode/ status: passed live_gate: status: complete_bounded_two_direction_proof apex_commit: d68ffc4 code_boss: PASS clean_detached_build_routes: 5 deployed_at: 2026-07-23T09:00:47-07:00 apex_http: 200 pages_hostname: https://discussionbridge-dev.pages.dev pages_hostname_redirect: 301_to_apex blog: route: /blog/every-connection-has-a-job/ http: 200 topic_id: 37 category_id: 5 wiki: false tags: [discussionbridge, community, product] guide: route: /guides/how-to-choose-a-discussion-bridge-source-mode/ http: 200 topic_id: 36 category_id: 6 wiki: true tags: [discussionbridge, source-mode, guide] source_disclosure: verified primary_comments: fullInteractive sync_existing_dry_run: skipped_discourse_managed_no_writeback npm_audit: { high: 1, automatic_fix: prohibited_not_run } known_warning: Mermaid_chunk_over_500k topic_36_editor_ownership_acceptance: status: open required: - change_first_post_owner_from_discourseadmin_to_selected_bridge_forum_editor - edit_wiki_as_selected_editor - import_refresh_discourse_managed_guide_with_overwrite - verify_content_and_source_ownership - build_deploy_and_verify_live_markers - prove_no_Astro_writebacksource_author_provenance: import_fields: - discussionSourceAuthorUsername - discussionSourceAuthorName overwrite_refresh: update_from_current_Discourse_first_post_author preserved_on_refresh: - discussionSourceMode - discussionSync_false - discourseTopicId rendered_notice: "Source author: Display Name (@username)" profile_link: same_forum_base_preserving_safe_/u/username subfolder_example: topic: https://example.com/forum/t/example/123 profile: https://example.com/forum/u/editorbridgeforum unsafe_username_behavior: omit_author_metadata_and_linkrequest_actor_regressions: - real_CLI_execution_with_--post-as - dry-run_actor_outputpackage_suite: 78/78category_contract: astro_managed: configured_category: authoritative_sync_corrects_drift no_configured_category: preserve_manual_Discourse_category discourse_managed_or_imported_source: category_writeback: prohibited multi_target: target_categories: independent evidence_record: docs/evidence/DISCUSSIONBRIDGE_DEV_TWO_WAY_DOGFOOD_2026-07-23.md count_boundary: apex_generated_public_routes: 5 docs_synced_sources: 20 docs_generated_html_pages: 21 direction_1: source: discussionbridge.dev_Astro_managed_blog destination: forum.discussionbridge.dev_public_companion_topic writer: Astro direction_2: source: forum.discussionbridge.dev_community_wiki destination: discussionbridge.dev_durable_public_guide source_mode: discourse-managed discussionSync: false source_provenance: required refresh: deterministic_reviewed_source destination_contract: explicit_public_route_and_Astro_navigation_lane primary_discussion: source_forum_topic writer: Discourse_wiki reply_streams: independent_never_merged public_outcome: The site starts conversations. The community develops durable knowledge. The site publishes what the community learns.support: formal_work: GitHub Issues setup_and_field_reports: Discourse Alpha Support category email_intake: alphasupport@discussionbridge.dev -> Discourse private_help: paid implementation/migrationattribution_and_licensing: implementation: script: scripts/check-attribution.mjs inventory: docs/THIRD_PARTY_PROVENANCE.json reviewed_override_evidence: docs/third-party-licenses/khroma-2.1.0-MIT.txt fresh_checkout_sync: executable: process.execPath shell: false regression: passed full_package_gate: command: npm run check:attribution included_in_package_suite: 73/73 status: PASS checks: - root_package_MIT_parity_and_holder - package_license_metadata - production_lock_dependency_license_allowlist_or_reviewed_override - npm_pack_required_contents - README_non_affiliation_and_rendered_links - tracked_media_provenance - protected_path_scan docs_scope_gate: command: npm run check:attribution --docs-scope required_status: PASS (docs scope) npm_package_contents: SKIPPED (requires built release candidate) docs_result: 20_synced_sources_21_HTML_pages reporting_invariant: package_73_of_73_and_docs_20_of_21_are_not_interchangeable manual_boss: required_result: "Attribution and Licensing: PASS / FAIL / N/A" reviewed_paths: required sanitized_exact_release_record: required current_status: PASS exact_candidate: b09dbce commit_chain: [7127eb1, 462b3ae, b09dbce] remaining_findings: 0 record: docs/evidence/ATTRIBUTION_LICENSING_REVIEW_B09DBCE_2026-07-23.mdrelease_prerequisites: - npm package name, ownership, and publisher authority confirmed - npm account 2FA or trusted publishing confirmed - exact reviewed semver prerelease selected - npm pack --dry-run and tarball contents inspected - clean install from packed artifact in plain Astro and Starlight - clean registry install from @alpha in plain Astro and Starlight - exports, Astro components, CLI bin/help, import/sync, comments, source disclosure, and multi-target helpers verified - LICENSE, README, repository, bugs, and homepage metadata verified - credentials, fixtures, local paths, and unintended files absent - Code Boss pass/fail result recorded against the exact release candidate - blocking code-review edits complete and re-reviewed where required - Bridge Boss technical verification complete - Manual Boss quality review complete - Human and Machine Manuals ready for the exact release - Product Boss documentation sign-off recorded - Product Boss release approval recorded - live Alpha Support category verified - email route verified - README, docs, metadata, demos, and release notes agree - GitHub prerelease and npm artifact resolve to the same commit - npm view, dist-tags, and clean consumer install verified - rollback, deprecation, and yank response documented for immutable versions - automatic npm audit fix prohibited - package tests and demo build pass - dry-run CLI checks pass - at least one controlled live smoke sync passesSupport bundles may include package/framework versions, sanitized commands and output, lane name, public URLs, category ID, tags, key type, and dry-run result. They must not include key values, credentials, private account data, or production secrets.
16. Durable Update Rule
Section titled “16. Durable Update Rule”When implementation confirms or changes a command, field, scope, endpoint, failure, recovery path, or deployment invariant:
- update this machine manual;
- update the corresponding human instruction when operator behavior changes;
- update the focused guide or checklist;
- route implementation gaps to Bridge Boss and manual-quality review to Manual Boss.