How-To: Managing Labs Projects
Introduction
This guide provides step-by-step procedures for every transition in the Labs project lifecycle — creating a Labs project from any of its three origins (fork, net-new, imported), tracking it through its time-box, graduating it to its production home, and sunsetting it when the evaluation ends.
Read the explanation guide first if you need the conceptual model — the why behind the gates, prohibitions, and approval paths. This document assumes you already know what state your project is in and what transition you need to make.
What this guide covers: the manual procedures that today connect a project's lifecycle states. The Fork Pipeline automates the creation step for fork-origin projects; everything else is a checklist executed by the owner with named approvers along the way.
Prerequisites
Before performing any Labs lifecycle operation, make sure you have:
- GitLab access: Maintainer (or higher) permissions on the relevant
wwnorton/lab/<initiative>subgroup so you can create projects, configure CI/CD, and (later) initiate a project transfer. If you don't have it, contact the Platform team via the@platformgroup tag in any public Digital Product Group MS Teams channel. - AWS access (for verification): SSO access to the target AWS account via AWS Identity Center so you can verify secrets, IAM roles, and CloudWatch logs during deployment and teardown. See Managing Application Secrets for Console access details.
- Fork Pipeline familiarity: If your project is fork-origin, read Using the Fork Pipeline before proceeding. This guide picks up where the Fork Pipeline leaves off.
kubectl+fluxCLI: Required for verification commands during graduation and sunset. Install via your normal tooling channel.- Tracking-tool access: Either Jira (today) or the GitLab
wwnorton/lab/labs-registryproject (going forward) — see Step: Register the project. During the migration window, both.
You will need both Jira and GitLab access during the migration window. Until the GitLab Issues + Topics tracking model is fully rolled out, every state transition must be reflected in both surfaces. The procedures below show both — do not skip either tab.
Creating a Labs Project
The creation procedure depends on origin. Pick the tab that matches yours.
- Origin: Fork
- Origin: New / PoC
- Origin: Imported / Third-Party
A fork is a copy of an existing wwnorton/app/* repository for parallel development. The Fork Pipeline does almost all of the mechanical work — your job here is to wrap it in the lifecycle ceremony (tracking record, time-box, success criteria).
Step 1: Get EM/Tech-lead sign-off
Before running any pipeline, get written sign-off from an EM or tech lead other than you. The sign-off confirms the work is real and the Labs slot is justified.
- Open a discussion in your team's preferred channel (MS Teams thread, Slack DM, GitLab Issue draft).
- Capture: project name, source upstream repo, initiative subgroup, your declared time-box (target end date), and written success criteria.
- The EM/Lead replies with explicit approval. Save the reference (link, screenshot, or quoted text) — it goes into the tracking record in Step 5.
No template required. The sign-off can live in a Jira comment, a Teams message, or a GitLab Issue draft — whatever surface your team already uses. What matters is that the record exists and can be linked to.
Step 2: Run the Fork Pipeline
Follow the full procedure in Using the Fork Pipeline. Key inputs for a Labs fork:
| Input | Value for Labs forks |
|---|---|
source_repo | The upstream repo, e.g. wwnorton/app/ec/order-service |
dest_group | wwnorton/lab/<initiative> — must exist; create the initiative subgroup first |
fork_name | Lowercase-hyphens, with a -fork suffix, e.g. order-service-cu-fork |
namespace | labs (default for the Labs group) |
Run with dry_run first, review the preview, then run for real. The pipeline produces three merge requests — note the URLs from the job summary.
Step 3: Open the Platform review ticket
All three MRs produced by the pipeline require Platform team review and approval before they can be merged. If you are not a Platform team member, you must open a review ticket immediately after the pipeline completes — before doing anything else.
Open the ticket following the procedure in Platform Team Review in the Fork Pipeline guide. The ticket must contain the two repo URLs, the three MR URLs in merge order, the source upstream, the destination Labs subgroup, and the namespace.
Platform team members forking for their own work can skip the ticket but must still go through GitLab's normal MR approval flow on each MR.
Do not start merging without approvals. The Deploy Repo MR sets up the Helm chart structure that the IAM and FluxCD MRs depend on; merging any of the three without Platform approval can leave the chart, terraform state, or cluster config in an inconsistent state.
Step 4: Merge the three pipeline MRs
Once Platform approval is recorded on each MR, follow the post-pipeline steps in the Fork Pipeline guide, paying close attention to:
- Update AWS secrets with new values before merging the Deploy Repo MR — the cloned secrets still point at the upstream's database and API keys.
- Merge order is enforced: Deploy Repo MR (after approval) → IAM Config MR (after approval, wait for terraform apply) → FluxCD Config MR (after approval).
- Verify the deployment in the
labsnamespace before moving on.
Step 5: Register the project in the tracker
See Register the project in the tracker below for the dual-surface procedure. Be sure to capture:
- The EM/Lead sign-off reference from Step 1
- The two GitLab project URLs (code +
-deploy) produced by the Fork Pipeline - The upstream repo URL — this is where the eventual graduation MR will be opened
- Origin: Fork
Once the tracker is updated, the fork is in the Incubating state and you're done with creation.
A net-new repository created directly in wwnorton/lab/<initiative>/ with no upstream parent. There is no automation for this today — every step is manual.
Step 1: Get EM/Tech-lead sign-off
Same as for forks (see the Fork tab). Capture: project name, initiative subgroup, time-box, success criteria. Save the reference.
Step 2: Create the code repo and the deploy repo
Both repos live in the same initiative subgroup and follow the -deploy naming convention.
- In GitLab, navigate to
wwnorton/lab/<initiative>and create the initiative subgroup if it does not exist. - Create the code repository:
- Path:
wwnorton/lab/<initiative>/<project> - Visibility: Internal
- Default branch:
main
- Path:
- Create the deploy repository:
- Path:
wwnorton/lab/<initiative>/<project>-deploy - Visibility: Internal
- Default branch:
main
- Path:
Bootstrap the deploy repo from an existing Labs project of similar shape:
git clone https://gitlab.com/wwnorton/lab/<other-initiative>/<other-project>-deploy.git
cd <other-project>-deploy
rm -rf .git
git init
git remote add origin https://gitlab.com/wwnorton/lab/<initiative>/<project>-deploy.git
Then update for your project:
chart/Chart.yaml—namefieldchart/templates/deployment.yaml— resource names,serviceAccountName, SPC reference, annotationschart/templates/service.yaml— service name, labels, selectorschart/templates/secretproviderclass.yaml— name,objectNamepaths,secretNameflux/{dev,qa,stg}/values.yaml— namespace (labs),imagePullSecret(image-pull-secret-lab), ingress host/path
Commit and push to main. The resulting deploy repo should mirror the structure produced by the Fork Pipeline.
Step 3: Add IAM and FluxCD config
The application's IAM role and FluxCD resources are managed in wwnorton/ops/infrastructure and the cluster-manager repo, respectively. Open two merge requests:
-
IAM Config MR against
wwnorton/ops/infrastructure:- Edit
accounts/{dev,qa,stg}/labs/config.json - Add an entry:
{ "name": "<project>", "type": "json" } - Get Platform team approval
- Merge and wait for
terraform applyto complete
- Edit
-
FluxCD Config MR against the cluster-manager repo:
- Add the GitRepository and HelmRelease resources for
<project>in thelabsnamespace - Reference the deploy repo from Step 2
- Use
flux-git-auth-labforsecretRefandimage-pull-secret-labfor image pulls - Merge only after the IAM Config terraform apply has completed — otherwise pods will fail to start due to missing service accounts
- Add the GitRepository and HelmRelease resources for
The same merge order applies as for fork-origin projects: IAM before FluxCD.
Step 4: Create AWS secrets
- Log in to AWS Secrets Manager in each lower-env account (dev/qa/stg).
- Create secrets at:
dev/labs/json/<project>qa/labs/json/<project>stg/labs/json/<project>
- Use real values from the start — there is no source to clone from.
Step 5: Register the project in the tracker
See Register the project in the tracker below. Be sure to capture:
- The EM/Lead sign-off reference from Step 1
- The two GitLab project URLs (code +
-deploy) - Origin: New
Once the tracker is updated, the project is in the Incubating state and you're done with creation.
External code being evaluated for absorption: vendor demos, OSS forks, hackathon prototypes. Identical to the New-project flow once provenance and security scanning are complete.
Step 1: Get EM/Tech-lead sign-off
Same as for the other origins. For Imported projects, also include in the sign-off discussion: the source URL, the upstream license, and the reason internal absorption is being considered.
Step 2: Attest provenance
Before any code lands in Labs, document where it came from. Create THIRD_PARTY_PROVENANCE.md in the repo root:
# Third-Party Provenance
- **Source**: https://github.com/<source>/<repo> (or vendor name)
- **Source commit / version**: <sha or version>
- **License**: <SPDX identifier, e.g. Apache-2.0>
- **Import date**: <YYYY-MM-DD>
- **Imported by**: <your name>
- **Reason**: <vendor eval / OSS evaluation / hackathon absorption / ...>
- **Modifications from source**: <none at import; list as you change them>
This file is read during the security review at graduation. Keep it current.
Step 3: Run baseline security scan
Run a dependency audit and a secret scan before pushing to Labs. The goal is to catch obvious problems before anyone else has to look at it.
# Example for a Node project — adapt to your stack
npm audit
git secrets --scan
Capture the output. Fix anything critical. Note unresolved findings in the tracking record for the security reviewer to consider at graduation.
Step 4: Import the code + create the deploy repo
For OSS sources, use GitLab's project import feature:
- In GitLab, New project → Import project → Repository by URL
- Source URL: the upstream repo
- Destination:
wwnorton/lab/<initiative>/<project> - Visibility: Internal
For vendor demos or hackathon code, create an empty wwnorton/lab/<initiative>/<project> and push from a clean local clone.
Then create the deploy repo following Steps 2 of the New-origin flow.
Step 5: Add IAM/Flux config + create AWS secrets
Follow Steps 3 and 4 of the New-origin flow. The procedure is identical.
Step 6: Register the project in the tracker
See Register the project in the tracker below. Be sure to capture:
- The EM/Lead sign-off reference from Step 1
- The two GitLab project URLs
- The provenance attestation reference
- Origin: Imported
Once the tracker is updated, the project is in the Incubating state and you're done with creation.
Step: Register the project in the tracker
Every Labs project lives in the tracker from the moment it moves from Proposed to Incubating. Maintain both Jira and GitLab during the migration window — these are not alternatives, they are parallel obligations.
- Today: Jira Epic
- Going forward: GitLab Issues + Topics
-
In your team's Jira project, create a new Epic.
-
Title:
[Labs] <project-name>(the[Labs]prefix is required — cross-project filtering depends on it). -
Assignee: the owner (you).
-
Description: fill in all of the following:
## Project Metadata- **Origin**: Fork | New | Imported- **Initiative**: <initiative subgroup name>- **EM/Lead approver**: <name> — sign-off ref: <link or quote>- **Entry date**: <YYYY-MM-DD>- **Target end date**: <YYYY-MM-DD>- **Co-owners**: <names, if any>## GitLab Projects- **Code repo**: <URL>- **Deploy repo**: <URL>- **Upstream repo** (Fork-origin only): <URL>- **Provenance attestation** (Imported-origin only): <URL to THIRD_PARTY_PROVENANCE.md>## Success Criteria<Numbered list of testable, measurable criteria. Written before the Epic moves to In Progress.>1. ...2. ...## Lifecycle State Log<One line per transition, newest at top.>- <YYYY-MM-DD> Incubating (entry approved by <approver>) -
Label / custom field: set the lifecycle state to
incubating. -
Save the Epic. Link to it from any related work items in the team's normal way.
- Apply Topics on the GitLab project(s) — both the code repo and the
-deployrepo:- Open each project → Settings → General → Topics
- Add:
labs-incubating, plus exactly one origin topic —origin-fork,origin-new, ororigin-imported
- Open a registry Issue in the
wwnorton/lab/labs-registryproject:- Title:
<project-name> - Description: the same markdown block shown in the Jira tab (Project Metadata, GitLab Projects, Success Criteria, Lifecycle State Log)
- Labels:
state::incubating,origin::fork|origin::new|origin::imported - Assignee: the owner
- Title:
- Cross-link: from the registry Issue, add the project URLs; from each project README or top-level docs, link back to the registry Issue.
During the migration window, do both. Open the Jira Epic AND apply the GitLab Topics AND open the registry Issue. They should agree at all times. The how-to procedures below assume both are updated on every transition.
Tracking & Time-Box Reviews
The owner is responsible for keeping the tracking surfaces current. Two events drive most updates: any lifecycle state transition, and arrival at the declared target end date.
Updating state on any transition
Whenever the project moves between states (incubating → graduating, graduating → migrated, incubating → sunsetting, etc.):
- In Jira: update the lifecycle-state label/custom field, and append a new line at the top of the
Lifecycle State Login the Epic description with the date, new state, and approver (if any). - In GitLab: update Topics on the project(s) (
labs-incubating→labs-graduating, etc.) and thestate::*label on the registry Issue. Add a comment on the registry Issue explaining the transition.
Do both on the same day. A mismatch between Jira and GitLab is treated as the project being out of compliance.
Time-box review at the declared end date
When the Target end date in the tracking record arrives:
- Decide on an action with input from the EM/Lead (if helpful) and any co-owners:
- Graduate → follow Graduating a Labs Project
- Sunset → follow Sunsetting a Labs Project
- Extend → set a new Target end date with written justification
- Record the decision in the tracking record:
- If extending: update the Target end date field and add a comment with the justification (one or two sentences explaining why the original window wasn't sufficient and what changed)
- If graduating: the state transition to
graduatingis the record - If sunsetting: the state transition to
sunsettingis the record
- Notify the EM/Lead of the action taken (a comment-link or a brief Teams message — no formal review required for extensions).
An expired Target end date with no action is the failure mode. At the quarterly portfolio review, any incubating project whose Target end date has passed without an extension comment is flagged. Don't be that project.
Quarterly portfolio review
The Platform team (or a designated delegate) runs a quarterly review across both tracking surfaces:
- Jira query — Epics with label
incubatingANDTarget end date < today - GitLab query — registry Issues with label
state::incubatingAND any custom date field past due
Findings are surfaced to the owners and their EMs/Leads. The goal is not to punish drift — it is to make drift visible.
Graduating a Labs Project
Graduation moves the project from Labs to its production home. The mechanics differ sharply by origin — pick the tab that matches yours.
Graduation prerequisites (all origins)
Before requesting graduation approval, complete all three:
-
Security review. Open a security-review request with the Platform team. The review covers:
- Secrets layout:
dev|qa|stg/labs/json/<project>contents, rotation posture - IAM configuration: service account, role annotations, policy scope
- Dependency scan results: from your CI's standard scanning step (
npm audit,pip-audit, etc.) plus any extra scan from Imported provenance - For Imported-origin projects: the
THIRD_PARTY_PROVENANCE.mdand license compatibility with the receiving group's posture
Attach the review outcome (approval comment or signed-off ticket) to the tracking record.
- Secrets layout:
-
CI tests passing + coverage threshold met. The CI pipeline must be green, and the coverage figure on the default branch must meet or exceed the bar set by the receiving group. Record both values (current coverage, required bar) in the tracking record.
-
Evidence against the entry-time success criteria. For each numbered criterion in the tracking record, add evidence (metrics, test results, screenshots, replayed-traffic results). Be specific. "Met" without evidence does not count.
Receiving group lead approval comes after these three are complete.
- Graduating a Fork
- Graduating New / Imported
A Fork-origin graduation does not move the Labs project anywhere. The work moves: a graduation MR is opened against the upstream repo, and once merged, the Labs fork is sunset.
Step 1: Sync from upstream
The Labs fork has diverged from upstream while you've been working. Pull in any changes that have landed on upstream main since the fork was created:
# In your local clone of the Labs fork
git remote add upstream https://gitlab.com/wwnorton/app/<group>/<app>.git
git fetch upstream
git merge upstream/main
# Resolve conflicts; run tests; push to the Labs fork
git push origin main
Re-run CI on the Labs fork and confirm it's still green. Re-deploy and verify in the labs namespace.
Step 2: Prepare the graduation MR against upstream
The graduation MR goes against the upstream repo (wwnorton/app/<group>/<app>), not the Labs fork.
-
From your local clone of the Labs fork, push a branch directly to the upstream repo (you need Developer+ access on upstream):
git remote add upstream https://gitlab.com/wwnorton/app/<group>/<app>.gitgit push upstream main:graduate/<initiative>-<short-description> -
Open the MR in upstream:
- Source branch:
graduate/<initiative>-<short-description> - Target branch:
main - Title:
Graduate <initiative>: <one-line summary> - Description: link to the Labs tracking record, list the success criteria + evidence, summarize the changes, attach the security review outcome
- Source branch:
-
The MR's CI runs against upstream's normal pipeline — including any production-tier gates (image scans, integration tests, etc.) that Labs didn't enforce.
Step 3: Receiving group lead approves and merges
The receiving group lead — typically the tech lead of the upstream's owning group — reviews and approves. Standard upstream MR review process applies; the Labs context is provided via the MR description.
Once merged, upstream's deploy pipeline runs and deploys the change to its normal envs (dev → qa → stg → prod) per the upstream's deployment policy.
Step 4: Verify upstream deployment
Wait for the upstream deployment to land and verify it in upstream's lower envs (dev/qa/stg). Run any smoke or integration tests the upstream team uses. Confirm with the receiving group lead that the change is healthy.
Do not sunset the Labs fork yet. The Labs deployment is your rollback if anything is wrong upstream.
Step 5: Sunset the Labs fork
Once upstream is verified healthy:
- Update the tracking record: state →
sunsetting, with a note that this is post-graduation cleanup (not a failed eval). - Follow Sunsetting a Labs Project for the full teardown sequence.
A New or Imported graduation moves the project itself via GitLab's native project transfer, followed by deploy-artifact migration into the receiving group's namespace.
Step 1: Identify receiving group and namespace
Work with the receiving group lead to confirm:
- Receiving GitLab group: e.g.
wwnorton/app/ec/ - Target Kubernetes namespace: e.g.
nas,ncia, etc. — notlabs - New ingress host/path (if different from the Labs one)
- Any rename: e.g. dropping a
-pocsuffix
Record these in the tracking record before starting the migration.
Step 2: Migrate AWS secrets to the new namespace
The Labs secrets at dev|qa|stg/labs/json/<project> need copies at the receiving namespace path. Use AWS CLI:
for ENV in dev qa stg; do
aws secretsmanager get-secret-value \
--secret-id "${ENV}/labs/json/<project>" \
--query SecretString --output text > /tmp/<project>-${ENV}.json
aws secretsmanager create-secret \
--name "${ENV}/<new-namespace>/json/<new-name>" \
--secret-string file:///tmp/<project>-${ENV}.json
rm /tmp/<project>-${ENV}.json
done
Do not leave the temp files on disk. Secrets contain credentials. The rm at the end of the loop is not optional. Consider running the script in a tmpfs directory.
Step 3: Update IAM config (terraform MR)
Open an MR against wwnorton/ops/infrastructure:
- Add an entry in
accounts/{dev,qa,stg}/<new-namespace>/config.json:{ "name": "<new-name>", "type": "json" } - Get Platform team approval, wait for
terraform planreview. - Merge and wait for
terraform applyto complete — this creates the service account and IAM role in the new namespace. - Verify:
kubectl get sa -n <new-namespace> | grep <new-name>
Do not remove the Labs IAM entry yet. The Labs deployment must keep running until prod is verified.
Step 4: Update FluxCD config (cluster-manager MR)
Open an MR against the cluster-manager repo:
- Add GitRepository + HelmRelease resources for
<new-name>in<new-namespace>. secretRef: use the production-group default (flux-git-authforwwnorton/app/*paths).imagePullSecret: same —image-pull-secret.- Reference the soon-to-be-transferred deploy repo by its post-transfer URL, e.g.
wwnorton/app/<group>/<new-name>-deploy. You will transfer the repos in Step 5; FluxCD will reconcile against the new path once it exists.
Merge order: do not merge this MR until after the project transfer in Step 5 completes — otherwise FluxCD will try to clone a repo path that doesn't exist yet.
Step 5: Transfer the GitLab projects
Transfer both the code repo and the deploy repo into the receiving group.
For each project (code, then -deploy):
- Open the project in GitLab
- Settings → General → Advanced → Transfer project
- Select the new namespace:
wwnorton/app/<group> - Confirm by typing the project path
GitLab transfers the project with full history, MRs, issues, and CI config preserved. Update any documentation or sidebar references that point to the old wwnorton/lab/* paths.
If renaming (e.g. dropping -poc), do the rename after transfer, via Settings → General → Advanced → Rename project.
Step 6: Verify the prod deployment
Now merge the FluxCD MR from Step 4. FluxCD will reconcile and deploy to the new namespace.
Verify the deployment in the receiving namespace:
# Pod status
kubectl get pods -n <new-namespace> -l app.kubernetes.io/name=<new-name>
# FluxCD reconciliation
flux get helmreleases -n <new-namespace> | grep <new-name>
# Service account
kubectl get sa <new-name>-sa -n <new-namespace> -o yaml
# Secrets mounted
kubectl exec -n <new-namespace> deploy/<new-name> -- ls -la /mnt/creds/
Have the receiving group lead confirm the deployment is healthy in dev/qa/stg before proceeding.
Step 7: Tear down the Labs deployment
Only after the receiving group lead has confirmed prod is verified:
- Update the tracking record: state →
migrated(terminal). Add a comment noting the new GitLab paths. - Tear down the Labs deployment by following Sunsetting a Labs Project, but skip the repo archive step — the repos no longer live in Labs; they were transferred in Step 5.
- Delete the now-unused Labs AWS secrets (
dev|qa|stg/labs/json/<project>) using the scheduled-delete procedure from the Sunset section.
Rollback During Graduation
If the graduation goes wrong mid-flight, the rollback strategy is don't tear down Labs until prod is verified. The Labs deployment is your safety net.
Point of no return: when is rollback off the table?
| Step in graduation | Rollback possible? | How |
|---|---|---|
| Before upstream MR / project transfer | Yes — easy | Discard the MR or pause the transfer. No prod-side state yet. |
| After upstream MR merged (Fork path) | Yes — revert | Open an upstream revert MR. Labs fork is still running as backup. |
| After project transfer (New path) | Yes — transfer back | GitLab Transfer the project back to wwnorton/lab/<initiative>. |
| After Labs deployment torn down | No | Labs is gone. From here, fix-forward in the new home. |
The instruction is simple: do not run the Labs teardown until the receiving group lead has signed off on the prod deployment being healthy. If you tear down Labs prematurely and prod has a problem, you are in fix-forward territory with no fallback.
What to do if a problem is found before the point of no return
- Pause the graduation. Update the tracking record state back to
incubatingwith a comment explaining the rollback. - Notify the EM/Lead and the receiving group lead.
- Reproduce in Labs. If the problem also reproduces in the Labs deployment, fix it there first. If it only reproduces in the new namespace, the problem is in the migration, not the code.
- Address the root cause. Common ones:
- Secrets didn't copy correctly (compare with
aws secretsmanager get-secret-value) - IAM role annotation missing (
kubectl get sa <name>-sa -n <ns> -o yaml | grep eks.amazonaws.com) - FluxCD reconciling against the wrong path (check the GitRepository URL)
- Namespace-specific config (CORS, ingress hostnames, env-specific URLs)
- Secrets didn't copy correctly (compare with
- Re-attempt the graduation from the step that failed.
Sunsetting a Labs Project
Sunset is run on two triggers: a failed evaluation, or post-graduation cleanup of a fork. The procedure is the same in both cases. The order of teardown matters — it follows the dependency graph so nothing is left orphaned.
Step 1: Tear down FluxCD resources
Remove the HelmRelease and GitRepository for the project in the labs namespace via an MR to the cluster-manager repo:
- Delete the
<project>.yaml(or equivalent) files referencing the project inclusters/*/labs/. - Open an MR, get approval, merge.
- FluxCD reconciles and removes the HelmRelease, which removes the underlying Deployment, Service, and SecretProviderClass.
Verify no resources remain:
kubectl get all -n labs -l app.kubernetes.io/name=<project>
# Expect: No resources found
kubectl get sa -n labs | grep <project>
# Expect: empty (the IAM service account is removed by the terraform step below)
Step 2: Schedule AWS secrets for deletion
Use scheduled deletion with a recovery window — not immediate delete. The default 30-day window lets an accidental sunset be reversed.
for ENV in dev qa stg; do
aws secretsmanager delete-secret \
--secret-id "${ENV}/labs/json/<project>" \
--recovery-window-in-days 30
done
For graduation cleanup (where the receiving namespace's secrets are the live ones), the Labs secrets being scheduled for deletion is expected — the prod path is <env>/<new-namespace>/json/<new-name> now.
Step 3: Remove IAM config (terraform MR)
Open an MR against wwnorton/ops/infrastructure removing the project's entry from accounts/{dev,qa,stg}/labs/config.json:
// Before
[
{ "name": "<project>", "type": "json" },
{ "name": "<other-project>", "type": "json" }
]
// After
[
{ "name": "<other-project>", "type": "json" }
]
Get Platform team approval, merge, and wait for terraform apply. The service account and IAM role for the project will be destroyed.
Verify:
kubectl get sa <project>-sa -n labs
# Expect: NotFound
Step 4: Archive the GitLab repositories
For each project (code, then -deploy):
- Open the project in GitLab
- Settings → General → Advanced → Archive project
- Confirm by typing the project path
Archive — do not delete. The repo becomes read-only, but history, MRs, issues, and CI runs are preserved for future reference.
Why archive over delete? A failed Labs project that is archived rather than deleted is the cheapest reference for the next contributor who proposes the same evaluation. Archive preserves the answer to "what did we try and why didn't it work."
Step 5: Update the tracking record
- In Jira: lifecycle state →
archived. Add a final comment in theLifecycle State Logwith the archive date and the trigger (failed eval / post-graduation cleanup). - In GitLab: update Topics to
labs-archivedon both projects, update the registry Issue label tostate::archived, close the registry Issue with a comment explaining the trigger.
The project is now done. Quarterly reviews will skip it.
Don't forget data resources
If the project provisioned S3 buckets, RDS instances, or other resources through the self-service Terraform modules, those are not removed by the steps above. See Cleaning Up Data Resources below.
Cleaning Up Data Resources
Data resources (S3 buckets, RDS instances) created via the self-service Terraform modules are owned by the project's owner and are removed via the same self-service workflow used to create them. The Platform team does not centrally clean these up — the owner does, before the GitLab repos are archived.
For each data resource the project provisioned:
| Resource type | Cleanup procedure |
|---|---|
| S3 bucket | Open an MR removing the bucket entry from accounts/<env>/s3/terraform.tfvars — How-To. Consider lifecycle rules to transition contents to Glacier before destroy if data should be preserved. |
| RDS instance | Open an MR removing the entry from the RDS terraform config — How-To. Take a final snapshot first if the data may be needed. |
| Route53 record | Open an MR removing the record — How-To. |
Order: data-resource cleanup happens after the application teardown (Steps 1–3 above) so the application is no longer trying to read or write the resource. Then archive the repos (Step 4).
FAQ
How do I know if my project should graduate or sunset when the time-box expires?
Compare the evidence you have against the success criteria you wrote at entry. If the evidence shows the criteria were met, graduate. If it shows they were not, sunset. If you genuinely don't know yet, extend the time-box with a written justification — but be honest: if you find yourself extending more than once without new information, that is itself a signal that the evaluation has failed.
What if no wwnorton/app/* group wants to receive my graduating project?
This is a real failure mode for New / Imported projects. The receiving group is identified by the owner together with the candidate group's lead — there is no central "graduations team" you can hand off to. If no group will own it, the project cannot graduate, and its options are: (a) extend the time-box while you keep looking, (b) reshape the work so it fits an existing group's scope, or (c) sunset.
For Fork-origin projects this is not usually a problem because the upstream's owning group is the natural receiver.
Can I have multiple owners on a Labs project?
The tracking record carries a single primary owner (the Jira assignee, the GitLab Issue assignee). Co-owners are listed in the description. The primary owner is the named human responsible for the lifecycle — they answer for the project at portfolio review and request the state transitions. Having one human's name on the record is what prevents the diffusion-of-responsibility failure mode.
My project hasn't done anything in a month. Is it abandoned?
Activity isn't the metric — the time-box and success criteria are. A project that has hit its criteria and is just waiting for a graduation review can sit quietly. A project that has done nothing because the owner has moved on is a tracking-record problem: update the state to sunsetting, notify the EM/Lead, and run the teardown.
Can I move a project out of Labs without graduating it?
No. The two terminal states are migrated (via graduation) and archived (via sunset). Moving a repo out of wwnorton/lab/ without going through graduation is the failure mode the lifecycle exists to prevent — it produces a production service with no security review, no test gates, and no receiving-group ownership.
What happens if I try to provision a production-tier resource from a Labs project?
The OPA policies on the self-service Terraform modules will reject the MR. This is the correct outcome — if your project needs production-tier infrastructure, it has outgrown Labs and should graduate.
What if I need a public-facing URL for a demo or external review?
Open a request with the Platform team. The review is short — the goal is to confirm that the URL is for a time-bound demo or review and not the beginning of shadow production. Approved demos typically get a one-off ingress that is scheduled for removal when the demo is over.
My Fork-origin project's upstream has changed significantly. Is graduation still possible?
Probably, but it depends. If the upstream changes are compatible with your fork's work, sync them in (git merge upstream/main) and proceed with the graduation MR. If the upstream has shifted in a direction that makes your fork's work no longer applicable, sunset the fork and re-evaluate whether the work is still worth doing against the new upstream.
Where do I file bugs found in a Labs project during evaluation?
In the Labs project's own GitLab Issues. The tracking record (Jira Epic / labs-registry Issue) is for the lifecycle state — not for day-to-day bug tracking.
Do I update the tracking record from a fork-pipeline run, or does the pipeline do it?
You do. The fork-pipeline creates the GitLab projects, builds the image, opens the three MRs, and clones the secrets — it does not touch Jira or the labs-registry. Adding the tracking record is the first thing you do after the pipeline runs (Step 4 of the Fork-origin creation flow).
Related Documentation
- Explanation: Understanding the Labs Project Lifecycle — the conceptual model, gates, and roles
- How-To: Using the Fork Pipeline — automation for fork-origin creation
- How-To: Managing S3 Buckets with Terraform — data-resource cleanup during sunset
- How-To: Managing RDS with Terraform — data-resource cleanup during sunset
- How-To: Managing Route53 with Terraform — DNS cleanup during sunset
- How-To: Managing Application Secrets — secrets handling during graduation and sunset