feat(deploy): Template Entries decide what a template deployment opens and shares (ADR-0081) - #346
Draft
aimeritething wants to merge 3 commits into
Draft
feat(deploy): Template Entries decide what a template deployment opens and shares (ADR-0081)#346aimeritething wants to merge 3 commits into
aimeritething wants to merge 3 commits into
Conversation
…s and shares
A Sealos Template may now declare `spec.entries.open` and
`spec.entries.share`, full URLs rendered with the template's own `${{ }}`
substitution. Open is what the Open control opens after the deployment;
Share is what the Success Record's share strip copies, shows as a QR code
and posts. Open falls back to the template's Sealos App CR `spec.data.url`
(212 catalog templates carry one; Brain applied it and never read it), then
to the automatic Default Open Port rule. Share falls back to the Open URL
only — never to the App CR url, which may embed `#token=` or
`/invite/<code>`.
Both entries are Deployment Access Endpoints with a declared URL (ADR
0079's contract): probed verbatim, query string kept, de-duplicated by full
URL, admitted to the record only once verified. They are optional
evidence — a declared link that fails its probe stays out of the record
instead of failing a deployment the Ingress-derived entries already prove
usable. When the Open URL matches an Ingress host and its longest-prefix
path to a Service port, Brain presets `brain.io/default-open-port` on that
Service unless the template already set it, so the AP Public Access Node's
Open and the record's Open agree.
The rules live in one pure module over rendered documents. Brain's own
renderer runs them before dumping the YAML, so the annotation ships with
the apply; for provider-applied templates the runner reads the same facts
back (template source, Instance defaults, in-memory inputs, Ingresses,
Services, App CR) once the Ingresses exist and patches the matched Service.
An entry no Ingress of the deployment serves is dropped, which also keeps
an unresolved expression from ever leaking into a URL.
The Success Record snapshots its share address as `shareUrl`; the section
shares that, falling back to the primary entry for records written before
the field existed. Old records keep loading unchanged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012nPEVVc7yiShA1SrfuTUqM
Records the 2026-09-08 decision that a template may declare its entries, with the fallback chains, the probe-verified-but-optional gate, the Service annotation preset, and the Ingress-host boundary. CONTEXT.md's Default Open Port no longer says a Template Instance has no open link and gains the Template Entry glossary term (Open Entry / Share Entry); the Deployment Access Endpoint and Success Record entries name the new source and the `shareUrl` snapshot. ADR-0079 and ADR-0080 get Status sections pointing at the amendment. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012nPEVVc7yiShA1SrfuTUqM
Decided with the product owner on 2026-09-09. A Template Entry's host is an Ingress host of the same deployment, and every such host is a required Deployment Access Endpoint whose probe already gates the Success Record. A second probe of the full URL would only test whether the application answers on that path, which the Public Address Health definition already excludes from health; and as optional evidence it silently swapped the declared Open for the automatic rule while the Service annotation named the declared port. So entries are no longer Deployment Access Endpoint cards. The record takes them as declared: the Open Entry is its first entry, sharing the Ingress card's entry when one lists the same URL, else added and headed by the App Listening Port it reaches when an AP observed that address. The Share Entry is only the record's share address and is never listed as an entry. shareUrl is still written on every new record, and the App CR fallback stays a fallback recorded only in ADR-0081. ADR-0081's verification section, ADR-0079's Status, the ADR index line, and CONTEXT.md's Template Entry, Deployment Access Endpoint, and Success Record entries say the same. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014ZuqvxhtEq1d99vxZSaiLE
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.
Decision (product owner, 2026-09-08 — documented in ADR-0081)
Sealos templates may declare Template Entries in their header:
Both are full URLs with the template's normal
${{ }}substitution. Open is what the Open control opens after a template deployment; Share is what the Success Record's share strip copies / QR-encodes / posts. Entries are not probed (decided 2026-09-09, below): an entry is kept only when an Ingress of the same deployment serves its host, and that host is a required Deployment Access Endpoint whose probe already gates the record; whether the app answers on a path is not routing health (CONTEXT.md, Public Address Health). The Open Entry is the record's first entry, headed by the App Listening Port it reaches when an AP observed that address (de-duplicated by full URL with the Ingress card's entry); the Share Entry is the record's share address and is never listed as an entry. When the Open URL maps to an App Listening Port (URL host = Ingress rule host → longest-prefix path → backend Service + port), Brain writesbrain.io/default-open-port: "<port>"on that Service unless the template preset it, so the AP Public Access Node's Open and the record's Open agree.This deliberately revises CONTEXT.md's Default Open Port sentence "there is no Project-level or Template-Instance-level open link"; ADR-0079 and ADR-0080 get Status sections pointing at the amendment.
Fallback chains
entries.openspec.data.url(app.sealos.io/v1App)entries.sharebrain.io/default-open-port(kept)Share never falls back to the App CR URL on its own (catalog App CR urls include
#token=…and/invite/<code>). An entry whose host no Ingress of the deployment serves is dropped; credentials and fragments disqualify an entry.Where it runs
sealos-templateartifacts): the rules run over the rendered docs before YAML is dumped, so the annotation ships with the apply.RenderedTemplateDeploymentgainsentries?.template-instance-pending/template-instance, i.e. the catalog path): after the Ingress read-back, the runner reads the template source (spec.entriesofftemplateYaml), the resolved defaults off the Instance CR, this run's in-memory inputs, and the Ingresses / Services / App CR from the cluster, applies the same pure rules, and PATCHes the matched Service. Any failure there degrades to "no declared entry" (console warning), never a failed deployment.Files changed
New
apps/ui/src/features/deploy/template-entries.ts— pure rules over rendered docs (+.test.ts, 13 tests)apps/ui/src/features/deploy/task/template-provider-entries.ts— provider-path read-back + Service patch (+.test.ts, 4 tests,globalThis.fetchstub)docs/adr/0081-declare-template-entries-for-open-and-share.mdChanged
apps/ui/src/lib/brain-labels.ts—BRAIN_DEFAULT_OPEN_PORT_ANNOTATIONapps/ui/src/features/deploy/template-renderer.ts— render entries with the resource context, preset annotation,renderTemplateEntryExpressionsfor the provider pathapps/ui/src/features/deploy/task/timeline.ts—DeploymentTemplateEntryUrls,shareUrlon the record (sanitizer + signature),deploymentTaskSuccessFromTimeline({ templateEntries, templateOpenEntryLabel })takes the entries as declared,deploymentTaskSuccessShareUrlapps/ui/src/features/deploy/task/result-readiness.ts—accessEndpointPortLabelForUrlexported (the Ingress-host naming rule, reused to head the Open Entry)apps/ui/src/features/deploy/task/runner.ts— wire both paths, head the Open Entry by its port, pass entries to the success builder; no cards for entriesapps/ui/src/features/deploy/deployment-task-success-section.tsx— share strip usesshareUrl(fallback: primary entry for old records)CONTEXT.md— Default Open Port paragraph revised; new glossary term Template Entry (Open Entry / Share Entry); Deployment Access Endpoint and Deployment Task Success Record updateddocs/adr/README.md,docs/adr/0079-*.md,docs/adr/0080-*.md— index line and Status sectionsVerification
Run from the repo root unless noted.
bun checkChecked 1641 files … No fixes applied.— cleanbun typecheck@sealai/ui#typecheckfails on one pre-existing error insrc/app/preview-canvas-perf/page.tsx(27,3)('calendar' does not exist in type …ContainerNodeQuickActionKey…). Reproduced identically on a cleanorigin/main(git stash -u && bun run typecheck); not touched by this PR.cd apps/ui && bun test src/features/deploy/template-entries.test.ts src/features/deploy/template-renderer.test.ts src/features/deploy/task/direct-timeline.test.ts src/features/deploy/task/timeline.test.tscd apps/ui && bun test src/features/deploy/task/result-readiness.test.tscd apps/ui && bun test src/features/deploy/task/template-provider-entries.test.tscd apps/ui && bun test src/features/deploy/task/runner.template-preserve.test.tscd apps/ui && bun test src/features/deploy(whole tree, one process)origin/main485 pass / 42 fail — the same 42 pre-existing failures (bunmock.moduleleaking across files inresult-readinessand the GitHub connection suites). No baseline-passing test regressed.Tests cover: entries parsing +
${{ }}substitution; the Open chain (entries.open → App CR → automatic); Share never derived from the App CR; Service annotation written on host+longest-prefix-path match (numbered and named ports), not written on no match, kept when preset; the Open Entry leading the record (de-duplicated with an Ingress card's entry, or added headed by its port, or by nothing when no AP observed it); the Share Entry asshareUrlonly, never an entry, and never a WS(S) URL; recordshareUrlsnapshot with fallback and old records without it; the share strip helper; provider-path read-back (Instance defaults, Service PATCH, preset respected, degrade on provider failure).Not verified (no live cluster)
POST /api/v2alpha/templates/instancesleaves resolvedspec.defaults[*].valueon the Instance CR (Brain's own renderer does; if the provider does not, entries render empty, fail the Ingress-host gate, and Open falls back to the App CR url — the code path is tested, the provider behaviour is not).k8s/getresolveskind=appstoapps.app.sealos.io(discovery matches plural/singular/shortname across lists;instancesalready works the same way).mergeDefaultOpenPortread of the presetted annotation on a real AP Product View.shareUrlthat differs from the primary entry.Decisions (product owner, 2026-09-09)
The four open questions this PR first carried were settled with the product owner:
shareUrlis written on every new record — the record is a snapshot; readers keep the primary-entry fallback for records written before the field.spec.entries. (No template-author docs live in this repo.)🤖 Generated with Claude Code
https://claude.ai/code/session_012nPEVVc7yiShA1SrfuTUqM