package: reject duplicate manifest names
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
## Unreleased
|
||||
|
||||
- `claro package doctor` now rejects duplicate package entries in `claro.lock` instead of allowing ambiguous lockfile data.
|
||||
- `claro package doctor` now rejects duplicate `name:` entries in a package manifest instead of accepting ambiguous package identity data.
|
||||
- `claro package doctor` now rejects listed package manifests whose `version:` only starts with the supported local package version, such as `version: 10`.
|
||||
- `claro package doctor` now rejects listed package manifests whose `source:` only starts with the supported local source, such as `source: local-extra`.
|
||||
- `claro package doctor` now rejects project files whose `manifest-version:` only starts with the supported value, such as `manifest-version: 10`.
|
||||
|
||||
@@ -108,6 +108,7 @@ Ready now:
|
||||
- `claro package doctor` requires the complete supported `source: local` value in each local package manifest, so a prefix such as `source: local-extra` is rejected
|
||||
- package project entries must be unique; `claro package doctor`, `list`, `add`, `init`, and `lock` reject duplicate names instead of generating ambiguous package or lockfile data
|
||||
- lockfile package entries must also be unique; `claro package doctor` reports duplicate lock entries instead of accepting ambiguous package/checksum data
|
||||
- package manifest `name:` entries must also be unique; `claro package doctor` reports `BAD package manifest name` instead of accepting ambiguous package identity data
|
||||
|
||||
Still needed:
|
||||
- install from local path
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ See `CURRENT_STATUS.md` for the detailed feature matrix.
|
||||
2. Keep examples aligned with the modern simple syntax (`END`, `DO`, short `SET`, short `ASK`) while documenting older compatibility forms separately.
|
||||
3. Expand validation around typecheck diagnostics and package/networking safety. Current Forgejo/Gitea CI runs the documented release gates (`claro validate`, typecheck diagnostics validation, version convention validation, package security validation, and CI workflow coverage validation). Current package safety validation covers safe project creation, rejecting unsafe project names during `claro new`, safe manifest/lockfile creation, rejecting unsafe package names during `package add`, detecting unsafe package names already present in `claro.project` during `package doctor`, refusing to write lockfile data for unsafe package names during `package lock`, making `package list` fail instead of displaying unsafe package entries as normal package names, making `package init` fail instead of reporting the project ready when unsafe package names are already present, making `package add` fail before changing files when unsafe package names are already present, making `package remove` fail instead of reporting success when unsafe package names remain during lockfile refresh, allowing `package remove` to remove an exact unsafe package entry so learners can repair a bad `claro.project`, making `package doctor` reject stale lockfile checksums for listed packages, and making `package doctor` reject lockfile package entries that are not listed in `claro.project`. Current function validation covers correct checked calls, wrong-type arguments, missing checked-argument diagnostics, missing unchecked-argument diagnostics for simple functions in both modern `DO greet` and empty compatibility `CALL greet WITH` forms, extra-argument diagnostics, and beginner-facing unknown-function diagnostics for mistyped modern `DO` and compatibility `CALL ... WITH` calls; current object-method parameter validation covers correct modern `DO object.method ...` and compatibility `CALL object.method WITH ...` checked calls, wrong-type diagnostics for both call forms, checked methods after another method in the same class, missing and extra checked-argument diagnostics including the extra-argument case for a checked method after another method in the same class, missing unchecked-argument diagnostics for simple object methods, missing-object guidance when `DO player.method ...` or compatibility `CALL object.method WITH ...` appears before `NEW` even if the method name is also wrong, and class-specific unknown-method diagnostics for both modern and compatibility calls when a learner calls a method the class does not declare; current object-field validation covers direct NUMBER/TEXT/YESNO field-assignment positives, direct object-field `CHECK TYPE` metadata positives for NUMBER/TEXT/YESNO fields, negative NUMBER/TEXT/YESNO field metadata mismatches, NUMBER/TEXT/YESNO-expectation unknown-field `CHECK TYPE` diagnostics, direct wrong-type field assignment diagnostics, field collection when a `HAS` field appears after a simple method, direct unknown-field diagnostics for NUMBER/TEXT/YESNO values, a beginner-facing fallback for unknown fields assigned from expressions whose type is not inferable yet, and missing-object diagnostics for both `SET object.field value` and `CHECK TYPE object.field IS TYPE` before `NEW`.
|
||||
3a. Keep package validation honest by checking that the project and each listed package manifest declare the exact supported manifest version, package version, local source, and expected package name.
|
||||
3b. Keep package security validation in Forgejo/Gitea CI so unsafe names, exact manifest-version/name/checksum mismatches, missing manifests, duplicate project packages, duplicate lock packages, and lockfile errors remain release blockers.
|
||||
3b. Keep package security validation in Forgejo/Gitea CI so unsafe names, exact manifest-version/name/checksum mismatches, missing manifests, duplicate project packages, duplicate lock packages, duplicate package manifest names, and lockfile errors remain release blockers.
|
||||
4. Add small examples for each foundation feature before adding bigger syntax.
|
||||
|
||||
## Complete-platform milestones
|
||||
|
||||
@@ -63,7 +63,7 @@ source: local
|
||||
checksum: 1234abcd
|
||||
```
|
||||
|
||||
`claro.lock` records the release version, lock format, packages, and checksums so future registry work has a stable safety foundation. `claro package doctor` checks those lockfile checksums for listed packages and reports `BAD lock checksum: name` if the lockfile is stale or edited incorrectly. It rejects duplicate `package:` entries with `DUPLICATE lock package: name`, so one package cannot have ambiguous lock data. It also reports `BAD lock package not in claro.project: name` when the lockfile contains a package entry that is not listed in `claro.project`, so learners know to refresh the lockfile instead of trusting stale package data. The project file must declare the complete supported `manifest-version: 1` value; a prefix such as `manifest-version: 10` is rejected as `BAD project manifest version: expected 1`. Package manifests must declare the complete supported `manifest-version: 1` value; a prefix such as `manifest-version: 10` is treated as a missing/unsupported manifest. They must also declare the complete expected `name:` value; a name that only starts with the package name is rejected as `BAD package manifest name: name`. The complete manifest `checksum:` value is checked too, so trailing or extra checksum text is rejected as `BAD package checksum: name`. Package manifests must also declare the complete supported local `version: 1` value and `source: local` value. Prefixes such as `version: 10` and `source: local-extra` are rejected as `BAD package version: name` and `BAD package source: name` rather than being accepted as valid local manifests.
|
||||
`claro.lock` records the release version, lock format, packages, and checksums so future registry work has a stable safety foundation. `claro package doctor` checks those lockfile checksums for listed packages and reports `BAD lock checksum: name` if the lockfile is stale or edited incorrectly. It rejects duplicate `package:` entries with `DUPLICATE lock package: name`, so one package cannot have ambiguous lock data. It also reports `BAD lock package not in claro.project: name` when the lockfile contains a package entry that is not listed in `claro.project`, so learners know to refresh the lockfile instead of trusting stale package data. The project file must declare the complete supported `manifest-version: 1` value; a prefix such as `manifest-version: 10` is rejected as `BAD project manifest version: expected 1`. Package manifests must declare the complete supported `manifest-version: 1` value; a prefix such as `manifest-version: 10` is treated as a missing/unsupported manifest. They must also declare exactly one expected `name:` value; duplicate or prefix-matching names are rejected as `BAD package manifest name: name`. The complete manifest `checksum:` value is checked too, so trailing or extra checksum text is rejected as `BAD package checksum: name`. Package manifests must also declare the complete supported local `version: 1` value and `source: local` value. Prefixes such as `version: 10` and `source: local-extra` are rejected as `BAD package version: name` and `BAD package source: name` rather than being accepted as valid local manifests.
|
||||
|
||||
## Project-name safety
|
||||
|
||||
|
||||
+1
-1
@@ -639,7 +639,7 @@ static int list_project_packages(void){ char *txt=read_file_text("claro.project"
|
||||
static int package_lock_checksum_ok(const char *name){ char *txt=read_file_text("claro.lock"); char *p; int in_pkg=0, ok=0; char expected[32]; if(!txt) return 0; package_checksum(name,expected,sizeof(expected)); p=txt; while(*p){ char *line=p; char save; while(*p&&*p!='\n'&&*p!='\r') p++; save=*p; *p=0; { char *t=trim_inplace(line); if(strnicmp2(t,"package:",8)==0){ char *pkg=trim_inplace(t+8); in_pkg=(strcmp(pkg,name)==0); } else if(in_pkg&&strnicmp2(t,"checksum:",9)==0){ char *sum=trim_inplace(t+9); ok=(strcmp(sum,expected)==0); break; } } if(save){ *p=save; p++; } while(*p=='\n'||*p=='\r') p++; } free(txt); return ok; }
|
||||
static int lock_packages_match_project(void){ char *txt=read_file_text("claro.lock"); char *p; char seen[128][65]; int seen_count=0; int ok=1; if(!txt) return 1; p=txt; while(*p){ char *line=p; char save; while(*p&&*p!='\n'&&*p!='\r') p++; save=*p; *p=0; { char *t=trim_inplace(line); if(strnicmp2(t,"package:",8)==0){ char *pkg=trim_inplace(t+8); int i; if(*pkg&&!package_name_safe(pkg)){ printf(" BAD lock package name: %s\n",pkg); ok=0; } else if(*pkg){ for(i=0;i<seen_count;i++){ if(strcmp(seen[i],pkg)==0){ printf(" DUPLICATE lock package: %s\n",pkg); ok=0; break; } } if(seen_count<128) snprintf(seen[seen_count++],sizeof(seen[0]),"%s",pkg); if(!package_has_name(pkg)){ printf(" BAD lock package not in claro.project: %s\n",pkg); ok=0; } } } } if(save){ *p=save; p++; } while(*p=='\n'||*p=='\r') p++; } free(txt); return ok; }
|
||||
static int package_manifest_version_matches(const char *text){ const char *p=text; while(p&&*p){ const char *line=p; const char *end; while(*p&&*p!='\n'&&*p!='\r') p++; end=p; if((size_t)(end-line)>=17&&strncmp(line,"manifest-version:",17)==0){ const char *value=line+17; while(value<end&&isspace((unsigned char)*value)) value++; while(end>value&&isspace((unsigned char)end[-1])) end--; return (size_t)(end-value)==1&&value[0]=='1'; } while(*p=='\n'||*p=='\r') p++; } return 0; }
|
||||
static int package_manifest_name_matches(const char *text,const char *name){ const char *p=text; size_t n=strlen(name); while(p&&*p){ const char *line=p; const char *end; while(*p&&*p!='\n'&&*p!='\r') p++; end=p; if((size_t)(end-line)>=5&&strncmp(line,"name:",5)==0){ const char *value=line+5; while(value<end&&isspace((unsigned char)*value)) value++; while(end>value&&isspace((unsigned char)end[-1])) end--; if((size_t)(end-value)==n&&strncmp(value,name,n)==0) return 1; } while(*p=='\n'||*p=='\r') p++; } return 0; }
|
||||
static int package_manifest_name_matches(const char *text,const char *name){ const char *p=text; size_t n=strlen(name); int fields=0; int matches=0; while(p&&*p){ const char *line=p; const char *end; while(*p&&*p!='\n'&&*p!='\r') p++; end=p; if((size_t)(end-line)>=5&&strncmp(line,"name:",5)==0){ const char *value=line+5; fields++; while(value<end&&isspace((unsigned char)*value)) value++; while(end>value&&isspace((unsigned char)end[-1])) end--; if((size_t)(end-value)==n&&strncmp(value,name,n)==0) matches++; } while(*p=='\n'||*p=='\r') p++; } return fields==1&&matches==1; }
|
||||
static int package_manifest_version_value_matches(const char *text){ const char *p=text; while(p&&*p){ const char *line=p; const char *end; while(*p&&*p!='\n'&&*p!='\r') p++; end=p; if((size_t)(end-line)>=8&&strncmp(line,"version:",8)==0){ const char *value=line+8; while(value<end&&isspace((unsigned char)*value)) value++; while(end>value&&isspace((unsigned char)end[-1])) end--; return (size_t)(end-value)==1&&value[0]=='1'; } while(*p=='\n'||*p=='\r') p++; } return 0; }
|
||||
static int package_manifest_source_matches(const char *text){ const char *p=text; while(p&&*p){ const char *line=p; const char *end; while(*p&&*p!='\n'&&*p!='\r') p++; end=p; if((size_t)(end-line)>=7&&strncmp(line,"source:",7)==0){ const char *value=line+7; while(value<end&&isspace((unsigned char)*value)) value++; while(end>value&&isspace((unsigned char)end[-1])) end--; return (size_t)(end-value)==5&&strncmp(value,"local",5)==0; } while(*p=='\n'||*p=='\r') p++; } return 0; }
|
||||
static int package_manifest_checksum_matches(const char *text,const char *expected){ const char *p=text; size_t n=strlen(expected); while(p&&*p){ const char *line=p; const char *end; while(*p&&*p!='\n'&&*p!='\r') p++; end=p; if((size_t)(end-line)>=9&&strncmp(line,"checksum:",9)==0){ const char *value=line+9; while(value<end&&isspace((unsigned char)*value)) value++; while(end>value&&isspace((unsigned char)end[-1])) end--; return (size_t)(end-value)==n&&strncmp(value,expected,n)==0; } while(*p=='\n'||*p=='\r') p++; } return 0; }
|
||||
|
||||
@@ -147,6 +147,15 @@ def main():
|
||||
fail(f"Package manifest source diagnostic was unclear:\n{out}")
|
||||
manifest_path.write_text(manifest, encoding="utf-8")
|
||||
|
||||
duplicate_name_manifest = manifest + "name: other-tools\n"
|
||||
manifest_path.write_text(duplicate_name_manifest, encoding="utf-8")
|
||||
rc, out = run([str(EXE), "package", "doctor"], work)
|
||||
if rc == 0:
|
||||
fail("package doctor must reject duplicate package manifest names")
|
||||
if "BAD package manifest name: math-tools" not in out:
|
||||
fail(f"Package duplicate manifest-name diagnostic was unclear:\n{out}")
|
||||
manifest_path.write_text(manifest, encoding="utf-8")
|
||||
|
||||
rc, out = run([str(EXE), "package", "doctor"], work)
|
||||
if rc != 0:
|
||||
fail(out)
|
||||
|
||||
Reference in New Issue
Block a user