Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh

Commit

A workspace alias resolves for Actions and for a token's reach, so g1t/deploy@v1 is flagon-io/deploy everywhere. uses: looks for an owner that is an alias (g1t for flagon-io) under its workspace's slug when nothing has the name as written, as the calling workspace sees it, and names what it finds by its own path; a g1t/ name answers ahead of GitHub only for a public repository and is never looked for on GitHub. The API decides a refused call again under the alias's workspace, so a token for flagon-io/deploy reaches it as g1t/deploy and nothing more. Actions release creates a missing g1t/<name> repository, public, through the API before pushing, and stops on a private one. The workspaces guide documents workspace aliases, and the actions guide g1t's own actions under the alias.

The site, the API and MCP, git over HTTPS and package registries already resolved aliases (identity's aliases.rs, migration 0029 seeds g1t for flagon-io); Actions and token reach were what still looked up g1t literally.

syntaqxcommitted Parenteb249dbBrowse files
6 files+272−260/6 viewed
+55−6
11 # Publishes g1t's own actions: each folder of actions/ (deploy, detect…)
22 # is pushed, as the whole of its own history, to the repository of the
3−# same name in the g1t workspace on g1t.sh (actions/deploy to g1t/deploy),
4−# and its major tag, v1, is moved to it. Workflows anywhere use them as
3+# same name under g1t/ on g1t.sh (actions/deploy to g1t/deploy), and its
4+# major tag, v1, is moved to it. Workflows anywhere use them as
55 # `uses: g1t/deploy@v1`. A change to an action that breaks how it is
66 # used gets a new major tag here (MAJOR below), and the old one stays.
77 #
99 # way (workflows/.g1t/workflows/deploy.yml is
1010 # `g1t/workflows/.g1t/workflows/deploy.yml@v1`).
1111 #
12+# `g1t` is a workspace alias: on g1t.sh it is flagon-io's, so g1t/deploy
13+# is the repository flagon-io/deploy, and the API, git and `uses:` all
14+# answer for it under either name (see the workspaces guide, "Workspace
15+# aliases"). This workflow names the alias throughout and never needs to
16+# know which workspace it leads to.
17+#
18+# A repository that does not exist yet is created first, public, through
19+# the API (POST /repos with `"workspace": "g1t"`, which the alias runs in
20+# flagon-io); one that exists but is private stops the run, as nobody
21+# outside its workspace could use it.
22+#
1223 # Runs on a push to main that changes actions/ or workflows/, and by hand.
1324 #
1425 # Needs the repository secret G1T_ACTIONS_PUBLISH_TOKEN: an access token of
15−# the g1t workspace with contents: write on its action repositories
16−# (g1t/deploy, g1t/detect, and one for each new folder, made public
17−# before its first release). The job's own token reaches this repository
18−# only, so it cannot push there.
26+# the workspace g1t leads to (flagon-io), with the scopes repo:write (to
27+# create a missing repository) and code:write (to push and move tags).
28+# A token without repo:write still publishes to repositories
29+# that exist, and the run says which one to create by hand. The job's own
30+# token reaches this repository only, so it can neither create nor push
31+# to the others.
1932 name: Actions release
2033
2134 on:
5063 env:
5164 PUBLISH_TOKEN: ${{ secrets.G1T_ACTIONS_PUBLISH_TOKEN }}
5265 SERVER: ${{ github.server_url }}
66+ API: ${{ github.api_url }}
5367 run: |
5468 set -euo pipefail
5569 if [ -z "${PUBLISH_TOKEN}" ]; then
5872 fi
5973 echo "::add-mask::${PUBLISH_TOKEN}"
6074 auth="Authorization: Basic $(printf 'g1t:%s' "${PUBLISH_TOKEN}" | base64 -w0)"
75+ bearer="Authorization: Bearer ${PUBLISH_TOKEN}"
6176 git config user.name g1t
6277 git config user.email g1t@users.noreply.g1t.sh
78+
79+ # The repository g1t/<name>, public: created when it is missing.
80+ ensure() {
81+ local name="$1" description="$2" found status
82+ found="$(mktemp)"
83+ status="$(curl -sS -o "${found}" -w '%{http_code}' -H "${bearer}" "${API}/repos/${NAMESPACE}/${name}")"
84+ case "${status}" in
85+ 200)
86+ if [ "$(jq -r '.is_private' "${found}")" != "false" ]; then
87+ echo "::error::$(jq -r '.namespace + "/" + .name' "${found}") is private, so no workflow outside its workspace could use ${NAMESPACE}/${name}. Make it public, then run this again."
88+ exit 1
89+ fi
90+ return 0
91+ ;;
92+ 404) ;;
93+ *)
94+ echo "::error::Looking for ${NAMESPACE}/${name} answered ${status}: $(cat "${found}")"
95+ exit 1
96+ ;;
97+ esac
98+ echo "Creating ${NAMESPACE}/${name}, public"
99+ status="$(jq -n --arg workspace "${NAMESPACE}" --arg name "${name}" --arg description "${description}" \
100+ '{ workspace: $workspace, name: $name, description: $description, private: false }' \
101+ | curl -sS -o "${found}" -w '%{http_code}' -X POST -H "${bearer}" -H "Content-Type: application/json" \
102+ --data-binary @- "${API}/repos")"
103+ if [ "${status}" != "201" ] && [ "${status}" != "200" ]; then
104+ echo "::error::Creating ${NAMESPACE}/${name} answered ${status}: $(cat "${found}"). Give G1T_ACTIONS_PUBLISH_TOKEN the repo:write scope, or create ${NAMESPACE}/${name} once by hand, public."
105+ exit 1
106+ fi
107+ echo "- Created \`$(jq -r '.namespace + "/" + .name' "${found}")\` for \`${NAMESPACE}/${name}\`" >> "${GITHUB_STEP_SUMMARY}"
108+ }
109+
63110 for dir in actions/*/; do
64111 name="$(basename "${dir}")"
65112 [ -f "${dir}action.yml" ] || { echo "::warning::${dir} has no action.yml; skipped."; continue; }
113+ ensure "${name}" "$(sed -n 's/^description: *//p' "${dir}action.yml" | head -n 1 | tr -d "\"'")"
66114 commit="$(git subtree split --prefix "actions/${name}" HEAD)"
67115 remote="${SERVER}/${NAMESPACE}/${name}.git"
68116 echo "Publishing actions/${name} (${commit}) to ${NAMESPACE}/${name}"
72120 done
73121 # g1t's reusable workflows, from workflows/, as g1t/workflows.
74122 if [ -d workflows/.g1t/workflows ]; then
123+ ensure workflows "g1t's reusable workflows, such as .g1t/workflows/deploy.yml."
75124 commit="$(git subtree split --prefix workflows HEAD)"
76125 remote="${SERVER}/${NAMESPACE}/workflows.git"
77126 echo "Publishing workflows (${commit}) to ${NAMESPACE}/workflows"
+49−1
140140 .find(|scope| !access.allows(*scope))
141141 }
142142
143+/// Whether a decision refuses the call.
144+fn refused(decision: &Option<Decision>) -> bool {
145+ decision.as_ref().is_some_and(|decision| !decision.allowed)
146+}
147+
143148 /// Runs `op` for `viewer`: enforcing an agent's scope first, and recording
144149 /// what happened.
145150 pub async fn run(
148153 viewer: &Viewer,
149154 input: &Value,
150155 ) -> Result<Outcome<Value>> {
151− let decision = decide(op, services, viewer, input);
156+ let mut input = std::borrow::Cow::Borrowed(input);
157+ let mut decision = decide(op, services, viewer, &input);
158+ // A token or an agent's run that reaches one repository knows it by its
159+ // workspace's slug. A call that names it under the workspace's alias
160+ // (`g1t/deploy` for `flagon-io/deploy`) or an old slug is decided again,
161+ // and run, under the slug it resolves to; only a refusal pays for the
162+ // lookup, and the call is allowed only as the repository it is.
163+ if refused(&decision)
164+ && let Some(retargeted) = crate::renamed::retarget(services, &input).await?
165+ {
166+ let again = decide(op, services, viewer, &retargeted);
167+ if !refused(&again) {
168+ decision = again;
169+ input = std::borrow::Cow::Owned(retargeted);
170+ }
171+ }
172+ let input = input.as_ref();
152173 if let Some(decision) = decision.as_ref().filter(|decision| !decision.allowed) {
153174 record(op, services, viewer, input, decision, None).await;
154175 return Ok(Outcome::fail(
280301 }
281302 }
282303
304+ /// A workflow job's token reaches its own repository, by its
305+ /// workspace's slug; named under the workspace's alias, the call is
306+ /// refused as written and decided again under the slug.
307+ #[test]
308+ fn a_repositorys_token_reaches_it_under_its_workspaces_alias() {
309+ let job = User {
310+ id: "usr_1".to_owned(),
311+ username: "syntaqx".to_owned(),
312+ token: Some(Box::new(TokenAccess {
313+ token_id: "tok_job".to_owned(),
314+ scopes: Some(vec![Scope::RepoRead.as_str().to_owned()]),
315+ repo: Some("flagon-io/deploy".to_owned()),
316+ ..TokenAccess::default()
317+ })),
318+ ..User::default()
319+ };
320+ let aliased = json!({ "repo": "g1t/deploy" });
321+ let first = token_decision(Op::GetRepo, &job, &aliased);
322+ assert!(refused(&first));
323+ let retargeted = crate::renamed::rewrite(&aliased, "g1t", "flagon-io");
324+ assert!(!refused(&token_decision(Op::GetRepo, &job, &retargeted)));
325+ // Another repository of the same workspace stays out of reach.
326+ let other = crate::renamed::rewrite(&json!({ "repo": "g1t/internal" }), "g1t", "flagon-io");
327+ assert!(refused(&token_decision(Op::GetRepo, &job, &other)));
328+ assert!(!refused(&None));
329+ }
330+
283331 #[test]
284332 fn a_session_is_limited_only_by_the_person_s_role() {
285333 let person = User { id: "usr_1".to_owned(), ..User::default() };
+23−4
280280
281281 | The repository | What happens |
282282 | --- | --- |
283−| On g1t and public: in your workspace, under `g1t/`, or with a name GitHub has no public repository of | Your workflows use it, from any workspace. |
283+| On g1t and public: in your workspace, under `g1t/` (g1t's own), or with a name GitHub has no public repository of | Your workflows use it, from any workspace. |
284284 | On g1t, private, in your workspace, with **Access** set to *Accessible from repositories in* the workspace | Your private repositories' workflows use it. A job reads it with a read-only token for that repository alone, which ends with the job. |
285285 | On g1t, private, and not shared that way | The step or job fails, and says why. A private repository's actions are never used by a public repository's workflows, whose logs anyone can read, nor from another workspace. |
286+| Under `g1t/`, and not one of g1t's own public repositories | The step or job fails, and says so. A `g1t/` name is never looked for on GitHub. |
286287 | Not on g1t, or private in a workspace you cannot see | An action comes from GitHub, at the commit its ref names there (see [actions from GitHub](#actions-from-github)); a reusable workflow is read from a public repository on GitHub. |
287288 | On g1t, public, in another workspace, with a name GitHub has a public repository of | GitHub's is used, as on GitHub: anyone can make a workspace, so a workspace called `actions` never answers for `actions/setup-node`. When g1t can't reach GitHub to tell, GitHub's is used too. |
288289
290+An owner that is a [workspace alias](/guides/workspaces/#workspace-aliases)
291+is looked for under its workspace's slug, by the same rules, as if you had
292+written that slug: the alias decides where g1t looks, never who may use
293+what it finds.
294+
289295 `ref` is a branch, a tag or a commit. A reusable workflow may be under
290296 `.g1t/workflows/` or `.github/workflows/`; a `.github/workflows/` path
291297 also finds the file under `.g1t/workflows/` in a repository moved to
327333
328334 ### g1t's own actions
329335
330−The `g1t` workspace on g1t.sh holds g1t's own actions, public, with a
331−`v1` tag that moves with each compatible release:
336+g1t's own actions live under `g1t/` on g1t.sh, public, with a `v1` tag
337+that moves with each compatible release. `g1t` is a
338+[workspace alias](/guides/workspaces/#workspace-aliases) of `flagon-io`,
339+the workspace of Flagon, Inc., which builds g1t: `g1t/deploy@v1` is the
340+repository `flagon-io/deploy`, and its page and the job's log name it
341+that way. Write `g1t/`; it keeps working wherever g1t's actions are kept.
332342
333343 | Action | What it does |
334344 | --- | --- |
335345 | [`g1t/deploy@v1`](https://g1t.sh/g1t/deploy) | Builds the checkout and puts it on [g1t.page](/guides/deployments/): production from the project's production branch, a preview from a pull request or another branch. The job needs `deployments: write`. See [deploying from a workflow](/guides/deployments/#deploying-from-a-workflow). |
336346 | [`g1t/detect@v1`](https://g1t.sh/g1t/detect) | Says what that build would find (`kind`, `install`, `build`, `output-dir`, `framework`) without building anything. |
337347
338−Both are composite actions around `g1t-runner`, which g1t's machines
348+g1t's reusable workflows are in `g1t/workflows` the same way, such as
349+`g1t/workflows/.g1t/workflows/deploy.yml@v1`, which a
350+[project's](/guides/projects/) deployments run.
351+
352+A `g1t/` name always means g1t's own: it answers even where GitHub has a
353+repository of the same name, only its public repositories answer for it,
354+and a `g1t/` name that is not one of them fails rather than being looked
355+for on GitHub.
356+
357+Both actions are composite actions around `g1t-runner`, which g1t's machines
339358 have; on a [self-hosted runner](/guides/self-hosted-runners/) they use
340359 the one on `PATH` or download the newest release.
341360
+30−0
158158 well-known GitHub action owner's name (such as `actions` or `docker`), and
159159 not another workspace's slug or someone else's username.
160160
161+## Workspace aliases
162+
163+An alias is a second name for a workspace that leads to it for good,
164+wherever a workspace's name goes. On g1t.sh, `g1t` is an alias of
165+`flagon-io`, the workspace of Flagon, Inc., which builds g1t and holds its
166+repositories: `g1t.sh/g1t/deploy` is the repository `flagon-io/deploy`.
167+
168+| | Behaviour |
169+| --- | --- |
170+| Web pages | Answer with a permanent redirect (301) to the same page under the workspace's slug, query string included. |
171+| `git clone`, `fetch`, `pull` and `push` | Answered in place, as the repository under the workspace's slug, with the same permissions, and no redirect for git to warn about. |
172+| API and MCP | A call that names the alias runs under the workspace's slug, and the answer names the workspace by its slug. A token that reaches one repository reaches it under the alias too, and nothing else. |
173+| Packages | Registry addresses under the alias redirect to the workspace's. |
174+| Actions | `uses: g1t/deploy@v1` and `g1t/workflows/.g1t/workflows/deploy.yml@v1` are found as `flagon-io/deploy` and `flagon-io/workflows`. See [g1t's own actions](/guides/actions/#g1ts-own-actions). |
175+
176+An alias resolves whatever its case (`G1T` is `g1t`), and follows the
177+workspace through renames: it points at the workspace, not at its slug.
178+Pages, API answers and links always show the workspace's own slug.
179+
180+An alias never changes who can see what. A private repository is found
181+under the alias only by someone who could see it under the workspace's
182+slug, and the alias gives nobody access they did not have.
183+
184+Aliases are set and removed by g1t's staff; there is no setting for them.
185+While an alias exists, nobody can register it as a username, create a
186+workspace with it, or rename a workspace to it. An alias is never a
187+person's or a workspace's name, and never one of the site's own routes.
188+[Contact us](https://g1t.sh/support) if your workspace needs one, such as
189+for a name it used to trade under.
190+
161191 ## Data residency
162192
163193 Data residency says where the git data of the workspace's new repositories
+110−12
1010 //! private one's code). When g1t does not have it (or the workspace cannot
1111 //! see it), the action comes from GitHub, as before, and the reusable
1212 //! workflow from a public repository there.
13+//!
14+//! An owner that is a workspace alias g1t's staff set (identity's
15+//! aliases.rs) is looked for under the workspace's own slug when nothing
16+//! has the name as written: `g1t/deploy@v1` is `flagon-io/deploy`, as
17+//! `g1t` is `flagon-io`'s alias. The repository found is named by its own
18+//! path from then on (the job's download, the called workflow's origin),
19+//! and is checked exactly as if it had been named that way: the alias
20+//! changes where g1t looks, never who may see or use what it finds.
21+//!
22+//! `g1t/` names g1t's own actions. Nobody can register `g1t`, so such a
23+//! repository answers for its name even where GitHub has one of the same
24+//! name, but only when it is public; and a `g1t/` name g1t does not have
25+//! (or that the workspace cannot see) is never taken from GitHub, where
26+//! anyone might own `g1t`.
1327
1428 use g1t_contracts::repos::{Repo, RepoPath};
1529 use g1t_contracts::{FailureCode, Outcome, User};
7589 Ok(())
7690 }
7791
92+/// The owner that names g1t's own actions and workflows (`g1t/deploy`):
93+/// a name nobody can register, an alias of the workspace that holds them
94+/// (`flagon-io` on g1t.sh).
95+pub(crate) const G1T_OWNER: &str = "g1t";
96+
97+/// Whether `uses:` names one of g1t's own actions or workflows, by the
98+/// owner as written.
99+pub(crate) fn names_g1ts_own(owner: &str) -> bool {
100+ owner.eq_ignore_ascii_case(G1T_OWNER)
101+}
102+
78103 /// Whether `target` on g1t answers for its `owner/repo` name even when
79104 /// GitHub has a repository of the same name: when it is in the caller's own
80−/// workspace, or is one of g1t's own (`g1t` is a name nobody can register).
81−/// Any other workspace's repository answers only for names GitHub doesn't
82−/// publish.
83−pub(crate) fn answers_for_its_name(caller: &Repo, target: &Repo) -> bool {
84− caller.namespace.eq_ignore_ascii_case(&target.namespace) || target.namespace.eq_ignore_ascii_case("g1t")
105+/// workspace, or is one of g1t's own (named under `g1t/`, as written in
106+/// `owner`, which reaches the workspace `g1t` is an alias of) and public.
107+/// A private one of g1t's own is for its own workspace, as any private
108+/// repository is. Any other workspace's repository answers only for names
109+/// GitHub doesn't publish.
110+pub(crate) fn answers_for_its_name(caller: &Repo, owner: &str, target: &Repo) -> bool {
111+ caller.namespace.eq_ignore_ascii_case(&target.namespace) || (names_g1ts_own(owner) && !target.is_private)
85112 }
86113
114+/// Whether an owner as written may be a workspace alias, and so is worth
115+/// asking identity about: a namespace's shape in any case, as aliases have
116+/// (identity reads it lowercased).
117+pub(crate) fn may_be_alias(owner: &str) -> bool {
118+ g1t_contracts::aliasable_name(owner).is_some()
119+}
120+
87121 /// Where another repository's action or workflow comes from.
88122 pub(crate) enum Found {
89123 /// g1t has it, and the caller may use it.
96130 /// Looks for `owner/repo` on g1t, as `caller`'s workspace (`ws`) sees
97131 /// it, and checks the caller may use it.
98132 pub(crate) async fn find_used(&self, caller: &Repo, ws: &User, used: &UsesRef) -> Result<Outcome<Found>> {
133+ let viewer = Some(ws.clone());
99134 let path = RepoPath { namespace: used.owner.clone(), name: used.repo.clone() };
100− let Some(target) = self.visible_repo(&path, &Some(ws.clone())).await? else {
135+ let mut found = self.visible_repo(&path, &viewer).await?;
136+ // Nothing has the name as written: an alias (`g1t` for `flagon-io`)
137+ // is looked for under its workspace's slug, as the same workspace
138+ // sees it, so a private repository there stays out of sight.
139+ if found.is_none()
140+ && let Some(slug) = self.alias_of(&used.owner).await?
141+ {
142+ found = self.visible_repo(&RepoPath { namespace: slug, name: used.repo.clone() }, &viewer).await?;
143+ }
144+ let Some(target) = found else {
145+ if names_g1ts_own(&used.owner) {
146+ return Ok(fail(
147+ FailureCode::NotFound,
148+ format!("{} is not one of g1t's actions or workflows: g1t's own are public repositories under g1t/.", used.full_name()),
149+ ));
150+ }
101151 return Ok(Outcome::Ok(Found::GitHub));
102152 };
103153 // A name GitHub publishes means GitHub's repository, unless the
104154 // repository on g1t is the caller's own workspace's or g1t's: anyone
105155 // can make a workspace called `actions`, and must not get to answer
106− // for `actions/setup-node`.
107− if !answers_for_its_name(caller, &target) && self.github_publishes(&used.full_name()).await? != Some(false) {
156+ // for `actions/setup-node`. A `g1t/` name is never GitHub's.
157+ if !answers_for_its_name(caller, &used.owner, &target)
158+ && !names_g1ts_own(&used.owner)
159+ && self.github_publishes(&used.full_name()).await? != Some(false)
160+ {
108161 return Ok(Outcome::Ok(Found::GitHub));
109162 }
110163 let level = if target.is_private && target.id != caller.id { self.access_level_of(&target.id).await? } else { "none" };
114167 })
115168 }
116169
170+ /// The slug of the workspace `owner` is an alias of, if it is one.
171+ async fn alias_of(&self, owner: &str) -> Result<Option<String>> {
172+ if !may_be_alias(owner) {
173+ return Ok(None);
174+ }
175+ let slug: Option<String> =
176+ g1t_kit::call(&self.identity, "resolve_alias", &g1t_contracts::identity::SlugArgs { slug: owner.to_owned() }).await?;
177+ Ok(slug.filter(|slug| !slug.eq_ignore_ascii_case(owner)))
178+ }
179+
117180 /// `job_action`: a running job asks where to fetch an action from, by
118181 /// `report.repository` (`owner/repo`) and `report.ref`. Answers
119182 /// `{"source": "g1t", "url", "ref", "token"}` (a read-only token for
585648 fn only_its_own_workspace_or_g1t_answers_for_a_name_github_has() {
586649 let web = repo("web", "acme", true);
587650 // The caller's own workspace, and g1t's own actions, whatever GitHub has.
588− assert!(answers_for_its_name(&web, &repo("setup", "acme", true)));
589− assert!(answers_for_its_name(&web, &repo("setup", "ACME", false)));
590− assert!(answers_for_its_name(&web, &repo("deploy", "g1t", false)));
651+ assert!(answers_for_its_name(&web, "acme", &repo("setup", "acme", true)));
652+ assert!(answers_for_its_name(&web, "acme", &repo("setup", "ACME", false)));
653+ assert!(answers_for_its_name(&web, "g1t", &repo("deploy", "g1t", false)));
591654 // Anyone else's public repository named like GitHub's `actions/setup-node`
592655 // does not: GitHub's is used when GitHub has one.
593− assert!(!answers_for_its_name(&web, &repo("setup-node", "actions", false)));
656+ assert!(!answers_for_its_name(&web, "actions", &repo("setup-node", "actions", false)));
657+ }
658+
659+ /// `g1t/deploy` is found as `flagon-io/deploy`, `g1t` being its alias.
660+ #[test]
661+ fn g1t_answers_through_its_alias_for_public_repositories_only() {
662+ let web = repo("web", "acme", true);
663+ let deploy = repo("deploy", "flagon-io", false);
664+ let internal = repo("internal", "flagon-io", true);
665+ // Written as g1t/deploy: g1t's own, public, answers for its name.
666+ assert!(answers_for_its_name(&web, "g1t", &deploy));
667+ assert!(answers_for_its_name(&web, "G1T", &deploy));
668+ // A private repository of the same workspace never does for another
669+ // workspace, and no other workspace may use it either way.
670+ assert!(!answers_for_its_name(&web, "g1t", &internal));
671+ assert!(may_use(&web, &internal, "organization").unwrap_err().contains("only repositories in flagon-io"));
672+ assert!(may_use(&repo("site", "acme", false), &internal, "none").is_err());
673+ // Named under flagon-io as written, it is only another workspace's.
674+ assert!(!answers_for_its_name(&web, "flagon-io", &deploy));
675+ // flagon-io's own repositories use theirs as their own.
676+ let own = repo("g1t", "flagon-io", true);
677+ assert!(answers_for_its_name(&own, "g1t", &internal));
678+ assert!(may_use(&own, &internal, "organization").is_ok());
679+ // A public one of g1t's own is used from anywhere, public or private.
680+ assert!(may_use(&web, &deploy, "none").is_ok());
681+ assert!(may_use(&repo("site", "acme", false), &deploy, "none").is_ok());
682+ }
683+
684+ #[test]
685+ fn only_namespace_shaped_owners_are_asked_about_as_aliases() {
686+ assert!(names_g1ts_own("g1t") && names_g1ts_own("G1t") && !names_g1ts_own("flagon-io"));
687+ assert!(may_be_alias("g1t"));
688+ assert!(may_be_alias("flagon") && may_be_alias("G1T"));
689+ for owner in ["some_org", "a.b", "-x", "x--y", "settings"] {
690+ assert!(!may_be_alias(owner), "{owner}");
691+ }
594692 }
595693 }
+5−3
1010 //! rename a workspace to it.
1111 //!
1212 //! An alias is resolved wherever an old slug is (`resolve_slug`): the site
13−//! and the API redirect or run again under the workspace's slug. Git over
14−//! HTTPS resolves it in place (`resolve_alias`), as pushes do not follow
15−//! redirects.
13+//! and the API redirect or run again under the workspace's slug (the API
14+//! also decides a token's reach again under it). Git over HTTPS resolves
15+//! it in place (`resolve_alias`), as pushes do not follow redirects, and
16+//! so does Actions for `uses:` (its reach.rs: `g1t/deploy@v1` is
17+//! `flagon-io/deploy`).
1618
1719 use g1t_contracts::identity::*;
1820 use g1t_contracts::time::rfc3339;

Comments

Sign in to comment.